322 lines
25 KiB
JavaScript
322 lines
25 KiB
JavaScript
import { readFile, writeFile } from 'node:fs/promises'
|
||
import { resolve } from 'node:path'
|
||
|
||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||
const outputPath = resolve(root, 'config/freecad-active-work-queue.json')
|
||
const statusOutputPath = resolve(root, 'docs/freecad-active-work-status.generated.zh-CN.md')
|
||
const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse)
|
||
const [gui, workflowPlan, composite, correlation, production, recoveredNaming, productionDriftClassification, orderedPairClassification, propertySemantics, exactPlan, platformCoverage, followUpProgress] = await Promise.all([
|
||
load('.cache/freecad/reference-desktop-gui-commands.json'),
|
||
load('config/freecad-gui-workflow-plan.json'),
|
||
load('config/freecad-composite-history-elementmap-oracle.json'),
|
||
load('config/freecad-tsn-stage-correlation-oracle.json'),
|
||
load('config/chrome-freecad-naming-production-verification.json'),
|
||
load('config/freecad-recovered-naming-classification.json'),
|
||
load('config/freecad-production-drift-classification.json'),
|
||
load('config/freecad-ordered-operation-pair-classification.json'),
|
||
load('config/freecad-native-property-semantics.json'),
|
||
load('config/freecad-web-exact-parity-plan.json'),
|
||
load('config/platform-module-coverage.json'),
|
||
load('config/freecad-follow-up-task-progress.json'),
|
||
])
|
||
|
||
const guiShardTasks = gui.guiCommands.mergedShards.map((shard, index) => ({
|
||
id: `ORA-GUI-SHARD-${String(index).padStart(3, '0')}`,
|
||
title: `Probe GUI command indexes [${shard.start}, ${shard.end})`,
|
||
status: 'completed',
|
||
dependencies: index === 0 ? ['ORA-GUI-SETUP-002'] : [`ORA-GUI-SHARD-${String(index - 1).padStart(3, '0')}`],
|
||
evidence: [shard.path, shard.sha256],
|
||
exit: `${shard.end - shard.start} commands have three native state observations`,
|
||
}))
|
||
const workflowTitles = [
|
||
'Select one GUI command family from the sensitivity report',
|
||
'Capture the native success workflow',
|
||
'Capture the native disabled workflow',
|
||
'Capture the native failure workflow',
|
||
'Capture the native cancel workflow',
|
||
'Capture recovery after failure or cancel',
|
||
'Update the exact blocker and focused checks',
|
||
]
|
||
const workflowProgress = new Map(workflowPlan.progress.map((entry) => [entry.taskId, entry]))
|
||
const workflowTasks = workflowTitles.map((title, index) => ({
|
||
id: `ORA-GUI-WF-${String(index).padStart(3, '0')}`,
|
||
title,
|
||
status: workflowProgress.get(`ORA-GUI-WF-${String(index).padStart(3, '0')}`).status,
|
||
dependencies: [index === 0 ? 'ORA-GUI-CONTEXT-001' : `ORA-GUI-WF-${String(index - 1).padStart(3, '0')}`],
|
||
...(index === 0 ? { family: workflowPlan.family.id, primaryCommand: workflowPlan.family.primaryCommand, exactTask: workflowPlan.family.primaryExactTask } : {}),
|
||
...(workflowProgress.get(`ORA-GUI-WF-${String(index).padStart(3, '0')}`).evidence.length > 0 ? { evidence: workflowProgress.get(`ORA-GUI-WF-${String(index).padStart(3, '0')}`).evidence } : {}),
|
||
exit: index === 0 ? 'one command family and its exact task are recorded' : 'one native workflow fixture and its focused assertion pass',
|
||
}))
|
||
const workflowClosed = workflowTasks.every((task) => task.status === 'completed')
|
||
const driftCases = composite.cases.filter((fixture) => fixture.mutation.metrics.namingRestorationDriftStages > 0)
|
||
const classifications = new Map(recoveredNaming.classifications.map((entry) => [entry.caseId, entry]))
|
||
if (classifications.size !== recoveredNaming.classifications.length) throw new Error('Recovered naming classifications contain duplicate cases.')
|
||
for (const entry of recoveredNaming.classifications) {
|
||
if (!driftCases.some(({ id }) => id === entry.caseId) || !['stable_semantics', 'allowed_evolution', 'implementation_defect'].includes(entry.classification)) throw new Error(`Recovered naming classification ${entry.caseId} is invalid.`)
|
||
}
|
||
const classifiedPrefix = driftCases.findIndex(({ id }) => !classifications.has(id))
|
||
const completedDriftCases = classifiedPrefix < 0 ? driftCases.length : classifiedPrefix
|
||
if (driftCases.slice(completedDriftCases).some(({ id }) => classifications.has(id))) throw new Error('Recovered naming classifications must form a contiguous serial prefix.')
|
||
const driftTasks = driftCases.map((fixture, index) => ({
|
||
id: `TSN-DRIFT-${String(index).padStart(3, '0')}`,
|
||
title: `Classify recovered naming evolution for ${fixture.id}`,
|
||
status: index < completedDriftCases ? 'completed' : workflowClosed && index === completedDriftCases ? 'in_progress' : 'pending',
|
||
dependencies: index === 0 ? ['ORA-GUI-WF-006'] : [`TSN-DRIFT-${String(index - 1).padStart(3, '0')}`],
|
||
caseId: fixture.id,
|
||
driftStages: fixture.mutation.metrics.namingRestorationDriftStages,
|
||
driftOrdinals: fixture.mutation.metrics.namingRestorationDriftOrdinals,
|
||
...(classifications.has(fixture.id) ? { classification: classifications.get(fixture.id).classification, evidence: ['config/freecad-recovered-naming-classification.json', classifications.get(fixture.id).reasonCode] } : {}),
|
||
exit: 'the case is classified as stable semantics, allowed evolution, or an implementation defect',
|
||
}))
|
||
const recoveredNamingClosed = completedDriftCases === driftCases.length
|
||
const productionDriftClassifications = new Map(productionDriftClassification.classifications.map((entry) => [entry.operation, entry]))
|
||
if (productionDriftClassifications.size !== productionDriftClassification.classifications.length) throw new Error('Production drift classifications contain duplicate operations.')
|
||
const productionDriftMutations = correlation.mutations.filter((mutation) => mutation.restoreTopologyDriftStages > 0)
|
||
const productionClassifiedPrefix = productionDriftMutations.findIndex(({ operation }) => !productionDriftClassifications.has(operation))
|
||
const completedProductionDrifts = productionClassifiedPrefix < 0 ? productionDriftMutations.length : productionClassifiedPrefix
|
||
if (productionDriftMutations.slice(completedProductionDrifts).some(({ operation }) => productionDriftClassifications.has(operation))) throw new Error('Production drift classifications must form a contiguous serial prefix.')
|
||
for (const entry of productionDriftClassification.classifications) {
|
||
if (!productionDriftMutations.some(({ operation }) => operation === entry.operation) || !['stable_semantics', 'allowed_evolution', 'implementation_defect'].includes(entry.classification)) throw new Error(`Production drift classification ${entry.operation} is invalid.`)
|
||
}
|
||
const productionDriftTasks = correlation.mutations
|
||
.filter((mutation) => mutation.restoreTopologyDriftStages > 0)
|
||
.map((mutation, index) => ({
|
||
id: `TSN-PROD-DRIFT-${String(index).padStart(3, '0')}`,
|
||
title: `Classify production restore topology drift for ${mutation.operation}`,
|
||
status: index < completedProductionDrifts ? 'completed' : recoveredNamingClosed && index === completedProductionDrifts ? 'in_progress' : 'pending',
|
||
dependencies: [index === 0 ? driftTasks.at(-1).id : `TSN-PROD-DRIFT-${String(index - 1).padStart(3, '0')}`],
|
||
operation: mutation.operation,
|
||
driftStages: mutation.restoreTopologyDriftStages,
|
||
...(productionDriftClassifications.has(mutation.operation) ? { classification: productionDriftClassifications.get(mutation.operation).classification, evidence: ['config/freecad-production-drift-classification.json', productionDriftClassifications.get(mutation.operation).reasonCode] } : {}),
|
||
exit: 'every changed downstream stage has a native comparison and classification',
|
||
}))
|
||
const productionDriftClosed = completedProductionDrifts === productionDriftTasks.length
|
||
const orderedPairClassifications = new Map(orderedPairClassification.classifications.map((entry) => [entry.pair, entry]))
|
||
if (orderedPairClassifications.size !== orderedPairClassification.classifications.length) throw new Error('Ordered operation pair classifications contain duplicate pairs.')
|
||
for (const entry of orderedPairClassification.classifications) {
|
||
if (!['accepted', 'rejected'].includes(entry.classification) || entry.nativeDecision !== entry.classification) throw new Error(`Ordered operation pair classification ${entry.pair} is invalid.`)
|
||
}
|
||
const coveredTransitions = new Set(['cut->rotate', 'rotate->fillet', 'fillet->mirrored', 'mirrored->linear-pattern', ...orderedPairClassifications.keys()])
|
||
let nextTransitionSelected = false
|
||
const transitionTasks = production.operations.flatMap((from) => production.operations.map((to) => {
|
||
const pair = `${from}->${to}`
|
||
const inProgress = productionDriftClosed && !coveredTransitions.has(pair) && !nextTransitionSelected
|
||
if (inProgress) nextTransitionSelected = true
|
||
return {
|
||
id: `TSN-PAIR-${from}-${to}`,
|
||
title: `Classify ordered operation pair ${pair}`,
|
||
status: coveredTransitions.has(pair) ? 'completed' : inProgress ? 'in_progress' : 'pending',
|
||
dependencies: coveredTransitions.has(pair) ? [] : [productionDriftTasks.at(-1).id],
|
||
pair,
|
||
...(orderedPairClassifications.has(pair) ? { classification: orderedPairClassifications.get(pair).classification, evidence: ['config/freecad-ordered-operation-pair-classification.json', orderedPairClassifications.get(pair).reasonCode] } : {}),
|
||
exit: 'native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence',
|
||
}
|
||
}))
|
||
|
||
const completionSteps = [
|
||
{ id: 'A', title: 'Lock native inventory and preconditions', exit: 'the native TypeId, inputs, defaults, dependencies and applicability are machine recorded' },
|
||
{ id: 'B', title: 'Capture native success evidence', exit: 'nominal and boundary successes record values, Shape, topology, status and diagnostics' },
|
||
{ id: 'C', title: 'Capture native failure and cancel evidence', exit: 'invalid input, disabled state, cancellation and document non-pollution are reproducible' },
|
||
{ id: 'D', title: 'Capture parameter mutation and restore evidence', exit: 'edit, recompute and restore return geometry and semantics to the classified native state' },
|
||
{ id: 'E', title: 'Implement the Facade capability', exit: 'the production Facade uses the declared native or explicit proxy path without hidden fallback' },
|
||
{ id: 'F', title: 'Close transaction and recovery behavior', exit: 'abort, undo, redo, stale, failure and resource ownership return to the expected state' },
|
||
{ id: 'G', title: 'Close FCStd save, reopen and resave', exit: 'FreeCAD to Web to FreeCAD persistence has zero unknown semantic drift' },
|
||
{ id: 'H', title: 'Replay in a real browser', exit: 'Chrome Worker, OPFS, UI state and resource release evidence pass' },
|
||
{ id: 'I', title: 'Promote capability and synchronize blockers', exit: 'all eight evidence tasks pass and machine reports alone advance the capability level' },
|
||
]
|
||
const slug = (value) => value.replaceAll('::', '-').replace(/[^A-Za-z0-9]+/g, '-').replace(/^-|-$/g, '').toLowerCase()
|
||
const makeCapabilityTasks = (prefix, capabilities, firstDependency) => {
|
||
const tasks = []
|
||
for (const capability of capabilities) {
|
||
for (const step of completionSteps) {
|
||
const id = `${prefix}-${slug(capability.id)}-${step.id}`
|
||
const previous = tasks.at(-1)?.id
|
||
tasks.push({
|
||
id,
|
||
title: `${step.title}: ${capability.title}`,
|
||
status: 'pending',
|
||
dependencies: [previous ?? firstDependency],
|
||
capability: capability.id,
|
||
phase: step.id,
|
||
...(capability.recordCount === undefined ? {} : { recordCount: capability.recordCount }),
|
||
evidenceRequired: step.id === 'I' ? ['all-prior-phases-pass', 'focused-check', 'exact-blocker-sync'] : ['one-native-or-production-artifact', 'one-focused-check'],
|
||
exit: step.exit,
|
||
})
|
||
}
|
||
}
|
||
return tasks
|
||
}
|
||
if (followUpProgress.schemaVersion !== 1 || followUpProgress.baseline?.freecadVersion !== '1.1.1' || followUpProgress.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || !Array.isArray(followUpProgress.completedTasks)) throw new Error('Follow-up task progress ledger is invalid.')
|
||
const recordedFollowUpIds = new Set()
|
||
for (const entry of followUpProgress.completedTasks) {
|
||
if (typeof entry.id !== 'string' || recordedFollowUpIds.has(entry.id) || !Array.isArray(entry.evidence) || entry.evidence.length === 0) throw new Error(`Follow-up progress entry ${entry.id} is invalid.`)
|
||
recordedFollowUpIds.add(entry.id)
|
||
}
|
||
const propertyCapabilities = propertySemantics.types
|
||
.filter(({ typeId, support }) => support === 'opaque-fcstd-proxy' || [...recordedFollowUpIds].some((id) => id.startsWith(`PROP-${slug(typeId)}-`)))
|
||
.map(({ typeId, recordCount }) => ({ id: typeId, title: typeId, recordCount }))
|
||
const carriedPromotedPropertyTypes = propertyCapabilities.filter(({ id }) => propertySemantics.types.find(({ typeId }) => typeId === id)?.support !== 'opaque-fcstd-proxy')
|
||
if (propertyCapabilities.length !== propertySemantics.supportSummary?.['opaque-fcstd-proxy']?.typeCount + carriedPromotedPropertyTypes.length) throw new Error('Opaque Property capability inventory is inconsistent.')
|
||
|
||
const followUpMilestoneDefinitions = [
|
||
{
|
||
id: 'PROPERTY-CODECS', exactTasks: ['EX-DOC-01'], title: 'Close every opaque Property type', prefix: 'PROP', capabilities: propertyCapabilities,
|
||
},
|
||
{
|
||
id: 'DOCUMENT-SEMANTICS', exactTasks: ['EX-DOC-02', 'EX-DOC-03', 'EX-DOC-04'], title: 'Close document, transaction and lifecycle semantics', prefix: 'DOC', capabilities: [
|
||
['observer-object-order', 'Object add/remove/rename observer order'],
|
||
['observer-property-order', 'Property add/remove/rename/change observer order'],
|
||
['nested-transactions', 'Nested commit/abort/undo/redo transactions'],
|
||
['dag-partial-recompute', 'DAG dirty propagation and partial recompute'],
|
||
['recompute-failure-recovery', 'Last-valid Shape and diagnostic recovery'],
|
||
['partial-document-load', 'Partial document load and PartialTrigger'],
|
||
['object-lifecycle', 'Copy/clone/delete/relink/group lifecycle'],
|
||
['extension-lifecycle', 'Extension schema, migration and unknown preservation'],
|
||
['feature-python-boundary', 'FeaturePython proxy, signature and execution boundary'],
|
||
['multi-document-links', 'External links, close/reopen, merge and recovery'],
|
||
].map(([id, title]) => ({ id, title })),
|
||
},
|
||
{
|
||
id: 'CORE-MODELING', exactTasks: ['EX-KER-01', 'EX-SK-01', 'EX-SK-02', 'EX-PART-01', 'EX-PD-01'], title: 'Close core modeling operations', prefix: 'CORE', capabilities: production.operations.map((operation) => ({ id: operation, title: `core operation ${operation}` })),
|
||
},
|
||
{
|
||
id: 'FCSTD-AND-FORMATS', exactTasks: ['EX-FC-01', 'EX-FMT-01', 'EX-FMT-02', 'EX-FMT-03', 'EX-FMT-04'], title: 'Close FCStd and interchange formats', prefix: 'IO', capabilities: [
|
||
['fcstd-readonly-roundtrip', 'FCStd unchanged object and Property round-trip'],
|
||
['fcstd-edited-roundtrip', 'Web edit, FreeCAD reopen/resave and Web reopen'],
|
||
['fcstd-shape-resources', 'Shape, ElementMap2, StringHasher, Expression and GuiDocument resources'],
|
||
['fcstd-unknown-resources', 'Unknown XML, BRep, script and Extension preservation'],
|
||
['fcstd-adversarial-input', 'Corrupt ZIP, traversal, compression limit and cancellation'],
|
||
['step', 'STEP units, colors, layers, names, assemblies and metadata'],
|
||
['iges', 'IGES units, colors, layers, names, assemblies and metadata'],
|
||
['brep', 'BREP tolerance, topology and editable round-trip'],
|
||
].map(([id, title]) => ({ id, title })),
|
||
},
|
||
{
|
||
id: 'GUI-CLOSURE', exactTasks: ['EX-UI-01', 'EX-UI-02', 'EX-UI-03', 'EX-UI-04', 'EX-UI-05'], title: 'Close full GUI behavior', prefix: 'GUI', capabilities: [
|
||
'application-shell', 'menus', 'toolbars', 'workbench-switching', 'mdi', 'combo-view', 'model-tree', 'task-panels', 'data-view-editors', 'dialogs', 'shortcuts', 'context-menus', 'status-report-jobs', '3d-selection', 'responsive-accessibility',
|
||
].map((id) => ({ id, title: id.replaceAll('-', ' ') })),
|
||
},
|
||
{
|
||
id: 'DOCUMENT-ENGINEERING-WORKBENCHES', exactTasks: ['EX-DOCWB-01', 'EX-ASM-01', 'EX-BIM-01', 'EX-SURF-01', 'EX-MESH-01', 'EX-MAT-01'], title: 'Close document and engineering workbenches', prefix: 'WB', capabilities: [
|
||
'draft', 'spreadsheet', 'techdraw', 'plot', 'cross-workbench-document', 'assembly', 'bim-ifc', 'surface', 'mesh-meshpart', 'material',
|
||
].map((id) => ({ id, title: id.replaceAll('-', ' ') })),
|
||
},
|
||
{
|
||
id: 'PLATFORM-SCRIPT-PROXY', exactTasks: ['EX-FEM-01', 'EX-CAM-01', 'EX-ROBOT-01', 'EX-DATA-01', 'EX-INSPECT-01', 'EX-SCRIPT-01', 'EX-ADDON-01', 'EX-PROXY-01', 'EX-PLATFORM-01'], title: 'Close platform, scripting and proxy capabilities', prefix: 'PLAT', capabilities: [
|
||
'fem', 'cam', 'robot', 'points-reverseengineering', 'inspection-measure', 'python-macro', 'addon', 'cloud', 'help', 'idf', 'jt', 'openscad', 'platform-boundaries',
|
||
].map((id) => ({ id, title: id.replaceAll('-', ' ') })),
|
||
},
|
||
{
|
||
id: 'QA-RELEASE', exactTasks: ['EX-QA-01', 'EX-QA-02', 'EX-QA-03', 'EX-REL-01'], title: 'Close QA and exact release', prefix: 'REL', capabilities: [
|
||
['chrome-baseline', 'Chrome locked baseline'],
|
||
['browser-matrix', 'Firefox, Safari and explicit unsupported boundaries'],
|
||
['os-gpu-matrix', 'Windows, macOS, Linux and GPU/software rendering'],
|
||
['accessibility-locale', 'Keyboard, screen reader, locale, long text and narrow viewport'],
|
||
['fuzz-soak-recovery', 'Fuzz, 1000 recomputes, long session, crash, migration and rollback'],
|
||
['resource-performance', 'Performance and WASM/JS/GPU/OPFS release accounting'],
|
||
['signed-release', 'SBOM, licenses, signing, offline install and rollback drill'],
|
||
['exact-promotion', '52-task, 34-module and zero-unknown promotion report'],
|
||
].map(([id, title]) => ({ id, title })),
|
||
},
|
||
]
|
||
|
||
let followUpDependency = transitionTasks.at(-1).id
|
||
const followUpMilestones = followUpMilestoneDefinitions.map((definition) => {
|
||
const tasks = makeCapabilityTasks(definition.prefix, definition.capabilities, followUpDependency)
|
||
followUpDependency = tasks.at(-1).id
|
||
return { id: definition.id, title: definition.title, exactTasks: definition.exactTasks, status: 'pending', tasks }
|
||
})
|
||
const followUpTasks = followUpMilestones.flatMap(({ tasks }) => tasks)
|
||
const followUpTaskIds = new Set(followUpTasks.map(({ id }) => id))
|
||
const completedFollowUpIds = new Set()
|
||
for (const entry of followUpProgress.completedTasks) {
|
||
if (!followUpTaskIds.has(entry.id) || completedFollowUpIds.has(entry.id) || !Array.isArray(entry.evidence) || entry.evidence.length === 0) throw new Error(`Follow-up progress entry ${entry.id} is invalid.`)
|
||
completedFollowUpIds.add(entry.id)
|
||
}
|
||
const completedFollowUpPrefix = followUpTasks.findIndex(({ id }) => !completedFollowUpIds.has(id))
|
||
const completedFollowUpCount = completedFollowUpPrefix < 0 ? followUpTasks.length : completedFollowUpPrefix
|
||
if (followUpTasks.slice(completedFollowUpCount).some(({ id }) => completedFollowUpIds.has(id))) throw new Error('Follow-up completed tasks must form one contiguous serial prefix.')
|
||
const orderedPairsClosed = transitionTasks.every(({ status }) => status === 'completed')
|
||
for (const [index, task] of followUpTasks.entries()) task.status = index < completedFollowUpCount ? 'completed' : orderedPairsClosed && index === completedFollowUpCount ? 'in_progress' : 'pending'
|
||
for (const milestone of followUpMilestones) milestone.status = milestone.tasks.every(({ status }) => status === 'completed') ? 'completed' : milestone.tasks.some(({ status }) => status === 'in_progress') ? 'in_progress' : 'pending'
|
||
|
||
const setupTasks = [
|
||
{ id: 'ORA-GUI-SETUP-000', title: 'Split GUI state probing from object/property probing', status: 'completed' },
|
||
{ id: 'ORA-GUI-SETUP-001', title: 'Diagnose the BIM first-run modal block', status: 'completed', dependencies: ['ORA-GUI-SETUP-000'] },
|
||
{ id: 'ORA-GUI-SETUP-002', title: 'Isolate FreeCAD config and suppress BIM first-run welcome before probing', status: 'completed', dependencies: ['ORA-GUI-SETUP-001'] },
|
||
]
|
||
const closureTasks = [
|
||
{ id: 'ORA-GUI-MERGE-000', title: 'Merge and verify all 100 GUI command shards', status: 'completed', dependencies: [guiShardTasks.at(-1).id] },
|
||
{ id: 'ORA-GUI-CONTEXT-000', title: 'Classify Import_ReadBREP as explicit module-import context', status: 'completed', dependencies: ['ORA-GUI-MERGE-000'] },
|
||
{ id: 'ORA-GUI-CONTEXT-001', title: 'Classify NaviCubeDraggableCmd as context-menu lazy registration', status: 'completed', dependencies: ['ORA-GUI-CONTEXT-000'] },
|
||
]
|
||
const milestones = [
|
||
{ id: 'ORA-GUI-BASELINE', exactTask: 'EX-ORA-01', status: 'completed', tasks: [...setupTasks, ...guiShardTasks, ...closureTasks] },
|
||
{ id: 'ORA-GUI-WORKFLOWS', exactTask: 'EX-ORA-01', status: workflowClosed ? 'completed' : 'in_progress', tasks: workflowTasks },
|
||
{ id: 'TSN-RECOVERY-DRIFT', exactTask: 'EX-TSN-04', status: productionDriftClosed ? 'completed' : workflowClosed ? 'in_progress' : 'pending', tasks: [...driftTasks, ...productionDriftTasks] },
|
||
{ id: 'TSN-ORDERED-PAIRS', exactTask: 'EX-TSN-04', status: orderedPairsClosed ? 'completed' : productionDriftClosed ? 'in_progress' : 'pending', tasks: transitionTasks },
|
||
...followUpMilestones,
|
||
]
|
||
const allTasks = milestones.flatMap((milestone) => milestone.tasks)
|
||
const allTaskIds = new Set(allTasks.map(({ id }) => id))
|
||
if (allTaskIds.size !== allTasks.length) throw new Error('FreeCAD active work queue contains duplicate task IDs.')
|
||
for (const milestone of followUpMilestones) for (const exactTask of milestone.exactTasks) if (!exactPlan.programs.some(({ tasks }) => tasks.some(({ id }) => id === exactTask))) throw new Error(`${milestone.id} references unknown exact task ${exactTask}.`)
|
||
for (const task of allTasks) for (const dependency of task.dependencies ?? []) if (!allTaskIds.has(dependency)) throw new Error(`${task.id} depends on unknown task ${dependency}.`)
|
||
const count = (status) => allTasks.filter((task) => task.status === status).length
|
||
const nextTask = allTasks.find((task) => task.status === 'in_progress')?.id
|
||
if (!nextTask) throw new Error('FreeCAD active work queue requires one in-progress task.')
|
||
if (count('in_progress') !== 1) throw new Error('FreeCAD active work queue requires exactly one in-progress task.')
|
||
const queue = {
|
||
schemaVersion: 1,
|
||
baseline: { freecadVersion: '1.1.1', commit: '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' },
|
||
updatedAt: '2026-08-15',
|
||
generatedBy: 'scripts/generate-freecad-active-work-queue.mjs',
|
||
policy: {
|
||
maxInProgress: 1,
|
||
unit: 'one artifact, one focused check, one explicit exit condition',
|
||
transitionUnit: 'one ordered operation pair',
|
||
driftUnit: 'one native case',
|
||
capabilityDefinition: completionSteps.map(({ id, title }) => `${id}: ${title}`),
|
||
followUpProgress: 'config/freecad-follow-up-task-progress.json',
|
||
},
|
||
nextTask,
|
||
summary: { tasks: allTasks.length, completed: count('completed'), inProgress: count('in_progress'), pending: count('pending') },
|
||
milestones,
|
||
}
|
||
const content = `${JSON.stringify(queue, null, 2)}\n`
|
||
const exactTasks = exactPlan.programs.flatMap(({ tasks }) => tasks)
|
||
const moduleCounts = Object.fromEntries(['exact', 'compatible', 'proxy', 'development'].map((level) => [level, platformCoverage.modules.filter((module) => module.level === level).length]))
|
||
const milestoneRows = queue.milestones.map((milestone) => {
|
||
const taskCount = (status) => milestone.tasks.filter((task) => task.status === status).length
|
||
const active = milestone.tasks.find((task) => task.status === 'in_progress')?.id ?? '-'
|
||
return `| \`${milestone.id}\` | ${milestone.tasks.length} | ${taskCount('completed')} | ${taskCount('in_progress')} | ${taskCount('pending')} | \`${active}\` |`
|
||
})
|
||
const statusContent = `<!-- Generated by scripts/generate-freecad-active-work-queue.mjs. Do not edit. -->
|
||
# FreeCAD 后续工作机器状态
|
||
|
||
生成日期:${queue.updatedAt}。基线:FreeCAD ${queue.baseline.freecadVersion} / \`${queue.baseline.commit}\`。
|
||
|
||
- 唯一活动任务:\`${queue.nextTask}\`
|
||
- 微任务:${queue.summary.tasks} total / ${queue.summary.completed} completed / ${queue.summary.inProgress} in_progress / ${queue.summary.pending} pending
|
||
- 有序操作对:${coveredTransitions.size}/${transitionTasks.length} classified / ${transitionTasks.length - coveredTransitions.size} unknown
|
||
- Property:${propertySemantics.supportSummary['native-editable-codec'].typeCount + propertySemantics.supportSummary['native-specialized-codec'].typeCount}/${propertySemantics.types.length} native types / ${propertySemantics.supportSummary['opaque-fcstd-proxy'].typeCount} opaque types
|
||
- exact 任务:${exactTasks.filter(({ status }) => status === 'completed').length}/${exactTasks.length} completed
|
||
- 模块能力:${moduleCounts.exact} exact / ${moduleCounts.compatible} compatible / ${moduleCounts.proxy} proxy / ${moduleCounts.development} development
|
||
|
||
| 里程碑 | 任务数 | completed | in_progress | pending | 当前任务 |
|
||
| --- | ---: | ---: | ---: | ---: | --- |
|
||
${milestoneRows.join('\n')}
|
||
|
||
详细任务、依赖、阶段、证据要求和退出条件见 \`config/freecad-active-work-queue.json\`。完成证据只能追加到 \`config/freecad-follow-up-task-progress.json\`,生成器会拒绝跳号、未知任务和无证据完成项。
|
||
`
|
||
if (process.argv.includes('--check')) {
|
||
const current = await readFile(outputPath, 'utf8').catch(() => '')
|
||
if (current !== content) throw new Error('FreeCAD active work queue is stale; run generate:freecad-active-work-queue.')
|
||
const currentStatus = await readFile(statusOutputPath, 'utf8').catch(() => '')
|
||
if (currentStatus !== statusContent) throw new Error('FreeCAD active work status is stale; run generate:freecad-active-work-queue.')
|
||
console.log(JSON.stringify({ status: 'freecad-active-work-queue-pass', nextTask: queue.nextTask, ...queue.summary }, null, 2))
|
||
} else {
|
||
await Promise.all([writeFile(outputPath, content), writeFile(statusOutputPath, statusContent)])
|
||
console.log(JSON.stringify({ status: 'freecad-active-work-queue-generated', output: outputPath, statusOutput: 'docs/freecad-active-work-status.generated.zh-CN.md', nextTask: queue.nextTask, ...queue.summary }, null, 2))
|
||
}
|