Files
Web_FreeCAD_Bitbybit/scripts/generate-freecad-active-work-queue.mjs
wangdequan d11566403d
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled
feat: close ordered pairs and property codec batches
2026-08-17 04:58:46 -04:00

322 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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))
}