coding-agent-harness 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/CONTRIBUTING.md +98 -0
- package/README.md +211 -86
- package/README.zh-CN.md +54 -34
- package/SKILL.md +25 -18
- package/docs-release/README.md +9 -5
- package/docs-release/architecture/overview.md +17 -5
- package/docs-release/architecture/overview.zh-CN.md +9 -5
- package/docs-release/assets/dashboard-overview.png +0 -0
- package/docs-release/guides/agent-installation.en-US.md +31 -8
- package/docs-release/guides/agent-installation.md +34 -9
- package/docs-release/guides/contributing.md +100 -0
- package/docs-release/guides/contributing.zh-CN.md +99 -0
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +3 -2
- package/docs-release/guides/document-audience-and-surfaces.md +3 -2
- package/docs-release/guides/full-legacy-migration-subagent-strategy.md +2 -2
- package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +2 -2
- package/docs-release/guides/legacy-migration-agent-prompt.md +0 -11
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +0 -11
- package/docs-release/guides/migration-playbook.en-US.md +14 -15
- package/docs-release/guides/migration-playbook.md +14 -15
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +7 -5
- package/docs-release/guides/parent-control-repository-pattern.md +7 -5
- package/docs-release/guides/preset-development.md +214 -0
- package/docs-release/guides/repository-operating-models.en-US.md +5 -4
- package/docs-release/guides/repository-operating-models.md +5 -4
- package/docs-release/guides/task-state-machine.en-US.md +207 -0
- package/docs-release/guides/task-state-machine.md +214 -0
- package/docs-release/intl/en-US.md +1 -1
- package/docs-release/intl/zh-CN.md +1 -1
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
- package/package.json +8 -3
- package/presets/legacy-migration/checks/preset-check.mjs +3 -0
- package/presets/legacy-migration/preset.yaml +134 -0
- package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
- package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
- package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
- package/presets/legacy-migration/templates/findings.seed.md +17 -0
- package/presets/legacy-migration/templates/review.seed.md +12 -0
- package/presets/legacy-migration/templates/task_plan.append.md +9 -0
- package/presets/legacy-migration/templates/visual_map.append.md +12 -0
- package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
- package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
- package/presets/lesson-sedimentation/preset.yaml +23 -0
- package/presets/lesson-sedimentation/templates/prompt.md +23 -0
- package/presets/module/preset.yaml +25 -0
- package/presets/module/templates/execution_strategy.append.md +8 -0
- package/presets/module/templates/task_plan.append.md +17 -0
- package/presets/standard-task/preset.yaml +31 -0
- package/presets/standard-task/templates/task_plan.append.md +7 -0
- package/references/adversarial-review-standard.md +2 -2
- package/references/agents-md-pattern.md +2 -2
- package/references/delivery-operating-model-standard.md +3 -3
- package/references/docs-directory-standard.md +6 -7
- package/references/harness-ledger.md +53 -96
- package/references/lessons-governance.md +88 -93
- package/references/module-parallel-standard.md +14 -14
- package/references/planning-loop.md +12 -6
- package/references/pull-request-standard.md +118 -0
- package/references/repo-governance-standard.md +11 -2
- package/references/review-routing-standard.md +7 -1
- package/references/ssot-governance.md +67 -59
- package/references/taskr-gap-analysis.md +600 -0
- package/references/walkthrough-closeout.md +7 -7
- package/scripts/check-harness.mjs +40 -301
- package/scripts/commands/dashboard-command.mjs +67 -0
- package/scripts/commands/migration-command.mjs +96 -0
- package/scripts/commands/preset-command.mjs +73 -0
- package/scripts/commands/task-command.mjs +327 -0
- package/scripts/harness.mjs +55 -260
- package/scripts/lib/capability-registry.mjs +66 -8
- package/scripts/lib/check-module-parallel.mjs +237 -0
- package/scripts/lib/check-profiles.mjs +61 -153
- package/scripts/lib/check-task-contracts.mjs +47 -0
- package/scripts/lib/core-shared.mjs +10 -0
- package/scripts/lib/dashboard-data.mjs +29 -6
- package/scripts/lib/dashboard-workbench.mjs +52 -12
- package/scripts/lib/dashboard-writer.mjs +14 -2
- package/scripts/lib/git-status-summary.mjs +46 -0
- package/scripts/lib/governance-index-generator.mjs +174 -0
- package/scripts/lib/governance-sync.mjs +514 -0
- package/scripts/lib/governance-table-boundary.mjs +175 -0
- package/scripts/lib/harness-core.mjs +5 -0
- package/scripts/lib/lesson-maintenance.mjs +36 -29
- package/scripts/lib/migration-support.mjs +1 -1
- package/scripts/lib/preset-audit-contracts.mjs +37 -0
- package/scripts/lib/preset-engine.mjs +497 -0
- package/scripts/lib/preset-registry.mjs +627 -0
- package/scripts/lib/preset-resource-contracts.mjs +83 -0
- package/scripts/lib/review-confirm-git-gate.mjs +248 -0
- package/scripts/lib/status-dashboard-renderer.mjs +102 -0
- package/scripts/lib/subagent-authorization-audit.mjs +196 -0
- package/scripts/lib/task-completion-consistency.mjs +16 -0
- package/scripts/lib/task-index.mjs +93 -0
- package/scripts/lib/task-lesson-candidates.mjs +242 -0
- package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
- package/scripts/lib/task-lifecycle/review-confirm.mjs +101 -0
- package/scripts/lib/task-lifecycle/review-gates.mjs +70 -0
- package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
- package/scripts/lib/task-lifecycle.mjs +297 -403
- package/scripts/lib/task-review-model.mjs +469 -0
- package/scripts/lib/task-scanner.mjs +130 -236
- package/scripts/lib/task-tombstone-commands.mjs +140 -0
- package/scripts/postinstall.mjs +14 -0
- package/skills/preset-creator/SKILL.md +179 -0
- package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
- package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -0
- package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
- package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
- package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
- package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
- package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
- package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
- package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
- package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
- package/skills/preset-creator/references/complex-task-skeleton/visual_map.md +50 -0
- package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
- package/templates/AGENTS.md.template +19 -15
- package/templates/dashboard/assets/app-src/00-state.js +1 -0
- package/templates/dashboard/assets/app-src/10-router.js +2 -1
- package/templates/dashboard/assets/app-src/20-overview.js +11 -5
- package/templates/dashboard/assets/app-src/30-tasks.js +92 -246
- package/templates/dashboard/assets/app-src/35-task-detail.js +246 -0
- package/templates/dashboard/assets/app-src/45-review.js +241 -22
- package/templates/dashboard/assets/app-src/50-migration.js +24 -10
- package/templates/dashboard/assets/app-src/90-bindings.js +171 -29
- package/templates/dashboard/assets/app.css +698 -156
- package/templates/dashboard/assets/app.css.manifest.json +9 -0
- package/templates/dashboard/assets/app.js +662 -91
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +342 -0
- package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
- package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
- package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +427 -0
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
- package/templates/dashboard/assets/i18n.js +123 -21
- package/templates/ledger/Harness-Ledger.md +13 -25
- package/templates/lessons/lesson-arch-process-change.md +1 -1
- package/templates/lessons/lesson-new-doc.md +1 -1
- package/templates/lessons/lesson-ref-change.md +1 -1
- package/templates/planning/execution_strategy.md +31 -0
- package/templates/planning/lesson_candidates.md +18 -6
- package/templates/planning/optional/artifacts/INDEX.md +3 -3
- package/templates/planning/optional/references/INDEX.md +3 -3
- package/templates/planning/review.md +59 -0
- package/templates/planning/task_plan.md +36 -13
- package/templates/reference/execution-workflow-standard.md +4 -3
- package/templates/reference/pull-request-standard.md +80 -0
- package/templates/reference/repo-governance-standard.md +7 -6
- package/templates/reference/review-routing-standard.md +6 -0
- package/templates/reference/walkthrough-standard.md +2 -1
- package/templates/verifier/verifier-output.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +20 -16
- package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
- package/templates-zh-CN/planning/execution_strategy.md +30 -0
- package/templates-zh-CN/planning/lesson_candidates.md +18 -6
- package/templates-zh-CN/planning/review.md +59 -1
- package/templates-zh-CN/planning/task_plan.md +30 -10
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/docs-library-standard.md +1 -1
- package/templates-zh-CN/reference/execution-workflow-standard.md +4 -3
- package/templates-zh-CN/reference/harness-ledger-standard.md +2 -2
- package/templates-zh-CN/reference/pull-request-standard.md +106 -0
- package/templates-zh-CN/reference/repo-governance-standard.md +4 -3
- package/templates-zh-CN/reference/review-routing-standard.md +8 -1
- package/templates-zh-CN/reference/walkthrough-standard.md +3 -2
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +1 -1
- package/docs-release/assets/dashboard-overview-en.png +0 -0
- package/scripts/smoke-dashboard.mjs +0 -92
- package/scripts/test-harness.mjs +0 -1395
- package/templates/ssot/Feature-SSoT.md +0 -43
- package/templates/ssot/Lessons-SSoT.md +0 -44
- package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
- package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
|
@@ -18,31 +18,48 @@ import {
|
|
|
18
18
|
firstColumn,
|
|
19
19
|
splitList,
|
|
20
20
|
splitDependencies,
|
|
21
|
-
getColumn,
|
|
22
21
|
} from "./markdown-utils.mjs";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
22
|
+
import {
|
|
23
|
+
isLessonCandidateDecisionComplete,
|
|
24
|
+
parseLessonCandidateStatus,
|
|
25
|
+
validateLessonCandidateDetailArtifacts,
|
|
26
|
+
} from "./task-lesson-candidates.mjs";
|
|
27
|
+
import {
|
|
28
|
+
assessMaterialsReadiness,
|
|
29
|
+
collectReviewRisks,
|
|
30
|
+
collectStateConflicts,
|
|
31
|
+
deriveLifecycleState,
|
|
32
|
+
deriveReviewQueueState,
|
|
33
|
+
deriveTaskQueues,
|
|
34
|
+
isBlockingReviewRisk,
|
|
35
|
+
parseAgentReviewSubmission,
|
|
36
|
+
parseReviewConfirmation,
|
|
37
|
+
parseTaskIdentity,
|
|
38
|
+
parseTaskTombstone,
|
|
39
|
+
requiresReviewMaterials,
|
|
40
|
+
taskReviewStatus,
|
|
41
|
+
taskScannerVersion,
|
|
42
|
+
} from "./task-review-model.mjs";
|
|
43
|
+
export {
|
|
44
|
+
collectReviewRisks,
|
|
45
|
+
deriveLifecycleState,
|
|
46
|
+
deriveReviewQueueState,
|
|
47
|
+
isBlockingReviewRisk,
|
|
48
|
+
parseAgentReviewSubmission,
|
|
49
|
+
parseReviewConfirmation,
|
|
50
|
+
parseTaskIdentity,
|
|
51
|
+
parseTaskTombstone,
|
|
52
|
+
requiresReviewMaterials,
|
|
53
|
+
taskReviewStatus,
|
|
54
|
+
taskScannerVersion,
|
|
55
|
+
} from "./task-review-model.mjs";
|
|
56
|
+
export {
|
|
57
|
+
allowedLessonCandidateRowStatuses,
|
|
58
|
+
allowedLessonCandidateTaskStatuses,
|
|
59
|
+
isLessonCandidateDecisionComplete,
|
|
60
|
+
parseLessonCandidateStatus,
|
|
61
|
+
reviewCompleteLessonCandidateStatuses,
|
|
62
|
+
} from "./task-lesson-candidates.mjs";
|
|
46
63
|
|
|
47
64
|
export function parseTaskState(progressContent) {
|
|
48
65
|
return parseTaskStateInfo(progressContent).state;
|
|
@@ -307,7 +324,11 @@ export function collectTasks(target) {
|
|
|
307
324
|
const visualMap = readVisualMapContractFile(taskDir, taskPlan);
|
|
308
325
|
const progress = readFileSafe(progressPath);
|
|
309
326
|
const review = readFileSafe(reviewPath);
|
|
310
|
-
const
|
|
327
|
+
const parsedLessonCandidates = parseLessonCandidateStatus(readFileSafe(lessonCandidatesPath));
|
|
328
|
+
const lessonDetailIssues = validateLessonCandidateDetailArtifacts(target, taskDir, parsedLessonCandidates);
|
|
329
|
+
const lessonCandidates = lessonDetailIssues.length
|
|
330
|
+
? { ...parsedLessonCandidates, issues: [...parsedLessonCandidates.issues, ...lessonDetailIssues] }
|
|
331
|
+
: parsedLessonCandidates;
|
|
311
332
|
const phases = parsePhases(visualMap.content);
|
|
312
333
|
const completion =
|
|
313
334
|
phases.length > 0
|
|
@@ -318,6 +339,8 @@ export function collectTasks(target) {
|
|
|
318
339
|
: 0;
|
|
319
340
|
const relative = toPosix(path.relative(target.projectRoot, taskDir));
|
|
320
341
|
const id = taskIdForDirectory(target, taskDir);
|
|
342
|
+
const identity = parseTaskIdentity(taskPlan, id);
|
|
343
|
+
const tombstone = parseTaskTombstone(taskPlan);
|
|
321
344
|
const title = titleFromMarkdown(brief.content || taskPlan, path.basename(taskDir));
|
|
322
345
|
const stateInfo = parseTaskStateInfo(progress);
|
|
323
346
|
const budget = parseTaskBudget(taskPlan);
|
|
@@ -329,13 +352,70 @@ export function collectTasks(target) {
|
|
|
329
352
|
const briefVisualStatus = explicitVisualMapStatus(brief.content);
|
|
330
353
|
const visualMapStatus = briefVisualStatus === "not-needed" && visualMap.status === "missing" ? "not-needed" : visualMap.status;
|
|
331
354
|
const risks = collectReviewRisks(review);
|
|
332
|
-
const
|
|
333
|
-
const
|
|
355
|
+
const reviewSubmission = parseAgentReviewSubmission(review, { taskKey: identity.taskKey });
|
|
356
|
+
const reviewConfirmation = parseReviewConfirmation(review, {
|
|
357
|
+
taskKey: identity.taskKey,
|
|
358
|
+
projectRoot: target.projectRoot,
|
|
359
|
+
taskDir,
|
|
360
|
+
reviewPath,
|
|
361
|
+
progressPath,
|
|
362
|
+
});
|
|
363
|
+
const reviewStatus = taskReviewStatus({ reviewContent: review, risks, confirmation: reviewConfirmation, submission: reviewSubmission });
|
|
334
364
|
const closeoutInfo = taskCloseoutInfo(target, taskPlanPath);
|
|
335
365
|
const lifecycleState = deriveLifecycleState({ state: stateInfo.state, reviewStatus, closeoutStatus: closeoutInfo.status });
|
|
366
|
+
const materialReadiness = assessMaterialsReadiness({
|
|
367
|
+
budget,
|
|
368
|
+
taskDir,
|
|
369
|
+
taskPlan,
|
|
370
|
+
brief,
|
|
371
|
+
visualMap,
|
|
372
|
+
reviewSubmission,
|
|
373
|
+
lessonCandidates,
|
|
374
|
+
phases,
|
|
375
|
+
longRunningContractPath,
|
|
376
|
+
reviewSurfaceRequired: requiresReviewMaterials({
|
|
377
|
+
state: stateInfo.state,
|
|
378
|
+
lifecycleState,
|
|
379
|
+
closeoutStatus: closeoutInfo.status,
|
|
380
|
+
}),
|
|
381
|
+
});
|
|
336
382
|
const stateConflicts = collectStateConflicts({ state: stateInfo.state, reviewStatus, closeoutStatus: closeoutInfo.status, lifecycleState });
|
|
383
|
+
const reviewQueueState = deriveReviewQueueState({
|
|
384
|
+
state: stateInfo.state,
|
|
385
|
+
lifecycleState,
|
|
386
|
+
reviewStatus,
|
|
387
|
+
closeoutStatus: closeoutInfo.status,
|
|
388
|
+
budget,
|
|
389
|
+
walkthroughPath: closeoutInfo.walkthroughPath,
|
|
390
|
+
lessonCandidateDecisionComplete: isLessonCandidateDecisionComplete(lessonCandidates),
|
|
391
|
+
materialsReady: materialReadiness.ready,
|
|
392
|
+
deletionState: tombstone.deletionState,
|
|
393
|
+
});
|
|
394
|
+
const queueModel = deriveTaskQueues({
|
|
395
|
+
id,
|
|
396
|
+
title,
|
|
397
|
+
state: stateInfo.state,
|
|
398
|
+
budget,
|
|
399
|
+
reviewStatus,
|
|
400
|
+
reviewSubmission,
|
|
401
|
+
reviewConfirmation,
|
|
402
|
+
reviewQueueState,
|
|
403
|
+
materialIssues: materialReadiness.issues,
|
|
404
|
+
risks,
|
|
405
|
+
stateConflicts,
|
|
406
|
+
lessonCandidates,
|
|
407
|
+
closeoutStatus: closeoutInfo.status,
|
|
408
|
+
tombstone,
|
|
409
|
+
taskDir,
|
|
410
|
+
target,
|
|
411
|
+
});
|
|
337
412
|
return {
|
|
338
413
|
id,
|
|
414
|
+
taskKey: identity.taskKey,
|
|
415
|
+
currentPath: `TARGET:${relative}`,
|
|
416
|
+
originalPath: `TARGET:${relative}`,
|
|
417
|
+
aliases: [],
|
|
418
|
+
identitySource: identity.identitySource,
|
|
339
419
|
shortId: path.basename(taskDir),
|
|
340
420
|
title,
|
|
341
421
|
path: `TARGET:${relative}`,
|
|
@@ -368,11 +448,19 @@ export function collectTasks(target) {
|
|
|
368
448
|
presetVersion: metadata.presetVersion,
|
|
369
449
|
migrationTargetLevel: metadata.migrationTargetLevel,
|
|
370
450
|
migrationAchievedLevel: metadata.migrationAchievedLevel,
|
|
371
|
-
evidenceBundle: metadata.evidenceBundle,
|
|
451
|
+
evidenceBundle: formatEvidenceBundle(metadata.evidenceBundle),
|
|
372
452
|
migrationSnapshot: collectMigrationSnapshot(target, metadata),
|
|
373
453
|
lifecycleState,
|
|
374
454
|
reviewStatus,
|
|
455
|
+
reviewSubmitted: Boolean(reviewSubmission?.submitted),
|
|
456
|
+
reviewSubmission,
|
|
457
|
+
reviewQueueState,
|
|
375
458
|
reviewConfirmation,
|
|
459
|
+
materialsReady: materialReadiness.ready,
|
|
460
|
+
materialIssues: materialReadiness.issues,
|
|
461
|
+
taskQueues: queueModel.taskQueues,
|
|
462
|
+
queueReasons: queueModel.queueReasons,
|
|
463
|
+
repairPrompt: queueModel.repairPrompt,
|
|
376
464
|
closeoutStatus: closeoutInfo.status,
|
|
377
465
|
walkthroughPath: closeoutInfo.walkthroughPath ? `TARGET:${closeoutInfo.walkthroughPath}` : "",
|
|
378
466
|
lessonCandidatePath: fs.existsSync(lessonCandidatesPath)
|
|
@@ -383,6 +471,7 @@ export function collectTasks(target) {
|
|
|
383
471
|
lessonCandidatePromotionState: lessonCandidates.promotionState,
|
|
384
472
|
lessonCandidateCloseoutToken: lessonCandidates.closeoutToken,
|
|
385
473
|
lessonCandidateRowCount: lessonCandidates.rows.length,
|
|
474
|
+
lessonCandidateRows: lessonCandidates.rows,
|
|
386
475
|
lessonCandidateOpenCount: lessonCandidates.openCount,
|
|
387
476
|
lessonCandidateIssues: lessonCandidates.issues,
|
|
388
477
|
lessonCandidateDecisionComplete: isLessonCandidateDecisionComplete(lessonCandidates),
|
|
@@ -390,6 +479,16 @@ export function collectTasks(target) {
|
|
|
390
479
|
? `TARGET:${toPosix(path.relative(target.projectRoot, longRunningContractPath))}`
|
|
391
480
|
: "",
|
|
392
481
|
longRunningContractStatus: fs.existsSync(longRunningContractPath) ? "present" : "missing",
|
|
482
|
+
deletionState: tombstone.deletionState,
|
|
483
|
+
supersededBy: tombstone.supersededBy,
|
|
484
|
+
supersedes: tombstone.supersedes,
|
|
485
|
+
deleteReason: tombstone.deleteReason,
|
|
486
|
+
hiddenByDefault: tombstone.hiddenByDefault,
|
|
487
|
+
reopenEligible: tombstone.reopenEligible,
|
|
488
|
+
archiveEligible: tombstone.archiveEligible,
|
|
489
|
+
tombstoneSourcePath: tombstone.tombstoneSourcePath
|
|
490
|
+
? `TARGET:${toPosix(path.relative(target.projectRoot, path.join(taskDir, "task_plan.md")))}#Task Tombstone`
|
|
491
|
+
: "",
|
|
393
492
|
stateConflicts,
|
|
394
493
|
completion,
|
|
395
494
|
phases,
|
|
@@ -432,136 +531,11 @@ function collectMigrationSnapshot(target, metadata) {
|
|
|
432
531
|
};
|
|
433
532
|
}
|
|
434
533
|
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
return emptyLessonCandidateStatus("missing", ["missing-candidate-file"]);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const fields = lessonCandidateFields(text);
|
|
442
|
-
const declaredStatus = normalizeLessonCandidateStatus(fields.get("task-level status") || "pending-review");
|
|
443
|
-
const reviewDecision = normalizeCandidateField(fields.get("review decision") || "pending-human-review");
|
|
444
|
-
const promotionState = normalizeCandidateField(fields.get("promotion state") || "not-promoted");
|
|
445
|
-
const closeoutToken = String(fields.get("closeout token") || "pending").trim();
|
|
446
|
-
const rows = lessonCandidateRows(text);
|
|
447
|
-
const issues = [];
|
|
448
|
-
|
|
449
|
-
if (!allowedLessonCandidateTaskStatuses.has(declaredStatus)) {
|
|
450
|
-
issues.push(`invalid-task-status:${declaredStatus}`);
|
|
451
|
-
}
|
|
452
|
-
for (const row of rows) {
|
|
453
|
-
if (!allowedLessonCandidateRowStatuses.has(row.status)) issues.push(`invalid-row-status:${row.id || "missing-id"}:${row.status}`);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const aggregateStatus = aggregateLessonCandidateStatus(rows, declaredStatus);
|
|
457
|
-
if (declaredStatus !== aggregateStatus && declaredStatus !== "missing") {
|
|
458
|
-
issues.push(`status-aggregate-mismatch:${declaredStatus}->${aggregateStatus}`);
|
|
459
|
-
}
|
|
460
|
-
if (aggregateStatus === "no-candidate-accepted" && !noCandidateReason(text)) {
|
|
461
|
-
issues.push("missing-no-candidate-reason");
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
return {
|
|
465
|
-
status: aggregateStatus,
|
|
466
|
-
declaredStatus,
|
|
467
|
-
schemaVersion: fields.get("schema version") || "",
|
|
468
|
-
reviewDecision,
|
|
469
|
-
promotionState,
|
|
470
|
-
closeoutToken,
|
|
471
|
-
rows,
|
|
472
|
-
openCount: rows.filter((row) => ["ready-for-review", "needs-promotion"].includes(row.status)).length,
|
|
473
|
-
issues,
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
export function isLessonCandidateDecisionComplete(candidateStatus) {
|
|
478
|
-
if (!candidateStatus || candidateStatus.issues?.length) return false;
|
|
479
|
-
return reviewCompleteLessonCandidateStatuses.has(candidateStatus.status);
|
|
534
|
+
function formatEvidenceBundle(value) {
|
|
535
|
+
const normalized = String(value || "").replace(/^TARGET:/, "").replace(/^\/+/, "");
|
|
536
|
+
return normalized ? `TARGET:${normalized}` : "";
|
|
480
537
|
}
|
|
481
538
|
|
|
482
|
-
function emptyLessonCandidateStatus(status, issues = []) {
|
|
483
|
-
return {
|
|
484
|
-
status,
|
|
485
|
-
declaredStatus: status,
|
|
486
|
-
schemaVersion: "",
|
|
487
|
-
reviewDecision: "",
|
|
488
|
-
promotionState: "",
|
|
489
|
-
closeoutToken: "",
|
|
490
|
-
rows: [],
|
|
491
|
-
openCount: 0,
|
|
492
|
-
issues,
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
function lessonCandidateFields(content) {
|
|
497
|
-
const { header, rows } = tableAfterHeading(content, /^Field$/i);
|
|
498
|
-
const fieldIndex = firstColumn(header, ["Field", "字段"]);
|
|
499
|
-
const valueIndex = firstColumn(header, ["Value", "值"]);
|
|
500
|
-
const fields = new Map();
|
|
501
|
-
if (fieldIndex < 0 || valueIndex < 0) return fields;
|
|
502
|
-
for (const row of rows) {
|
|
503
|
-
const key = String(row[fieldIndex] || "").trim().toLowerCase();
|
|
504
|
-
if (key) fields.set(key, String(row[valueIndex] || "").trim());
|
|
505
|
-
}
|
|
506
|
-
return fields;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function lessonCandidateRows(content) {
|
|
510
|
-
const { header, rows } = tableAfterHeading(content, /^ID$/i);
|
|
511
|
-
const idIndex = firstColumn(header, ["ID", "候选 ID"]);
|
|
512
|
-
const statusIndex = firstColumn(header, ["Row Status", "行状态", "Status", "状态"]);
|
|
513
|
-
const titleIndex = firstColumn(header, ["Title", "标题"]);
|
|
514
|
-
const decisionIndex = firstColumn(header, ["Review Decision", "审查决定"]);
|
|
515
|
-
const targetIndex = firstColumn(header, ["Promotion Target", "沉淀目标"]);
|
|
516
|
-
if (idIndex < 0 || statusIndex < 0) return [];
|
|
517
|
-
return rows
|
|
518
|
-
.filter((row) => /^LC-[A-Za-z0-9-]+$/i.test(row[idIndex] || ""))
|
|
519
|
-
.map((row) => ({
|
|
520
|
-
id: row[idIndex] || "",
|
|
521
|
-
status: normalizeLessonCandidateStatus(row[statusIndex] || ""),
|
|
522
|
-
title: row[titleIndex] || "",
|
|
523
|
-
reviewDecision: row[decisionIndex] || "",
|
|
524
|
-
promotionTarget: row[targetIndex] || "",
|
|
525
|
-
}));
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function normalizeLessonCandidateStatus(value) {
|
|
529
|
-
return String(value || "")
|
|
530
|
-
.replace(/`/g, "")
|
|
531
|
-
.trim()
|
|
532
|
-
.toLowerCase()
|
|
533
|
-
.replaceAll("_", "-")
|
|
534
|
-
.replace(/\s+/g, "-");
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
function normalizeCandidateField(value) {
|
|
538
|
-
return String(value || "").replace(/`/g, "").trim().toLowerCase().replaceAll("_", "-").replace(/\s+/g, "-");
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function aggregateLessonCandidateStatus(rows, declaredStatus) {
|
|
542
|
-
if (rows.length === 0) return declaredStatus === "no-candidate-accepted" ? "no-candidate-accepted" : declaredStatus;
|
|
543
|
-
const statuses = rows.map((row) => row.status);
|
|
544
|
-
if (statuses.includes("ready-for-review")) return "pending-review";
|
|
545
|
-
if (statuses.includes("needs-promotion")) return "needs-promotion";
|
|
546
|
-
if (statuses.every((status) => status === "promoted")) return "promoted";
|
|
547
|
-
if (statuses.every((status) => status === "rejected")) return "rejected";
|
|
548
|
-
if (statuses.every((status) => ["promoted", "rejected"].includes(status))) return "promoted";
|
|
549
|
-
return declaredStatus;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function noCandidateReason(content) {
|
|
553
|
-
const lines = String(content || "").split(/\r?\n/);
|
|
554
|
-
const start = lines.findIndex((line) => /^##\s*No-Candidate Reason\s*$/i.test(line.trim()));
|
|
555
|
-
if (start < 0) return "";
|
|
556
|
-
const body = [];
|
|
557
|
-
for (const line of lines.slice(start + 1)) {
|
|
558
|
-
if (/^##\s+/.test(line)) break;
|
|
559
|
-
body.push(line);
|
|
560
|
-
}
|
|
561
|
-
return body.join("\n").replace(/`/g, "").trim();
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
539
|
function taskCloseoutInfo(target, taskPlanPath) {
|
|
566
540
|
const closeout = readFileSafe(path.join(target.docsRoot, "10-WALKTHROUGH/Closeout-SSoT.md"));
|
|
567
541
|
if (!closeout.trim()) return { status: "missing", walkthroughPath: "" };
|
|
@@ -585,91 +559,11 @@ function extractWalkthroughPath(target, closeoutLine) {
|
|
|
585
559
|
return projectRelative;
|
|
586
560
|
}
|
|
587
561
|
|
|
588
|
-
export function parseReviewConfirmation(reviewContent) {
|
|
589
|
-
const match = String(reviewContent || "").match(/^##\s*(?:Human Review Confirmation|人工审查确认)\s*$([\s\S]*?)(?=^##\s+|\s*$)/im);
|
|
590
|
-
if (!match) return null;
|
|
591
|
-
const block = match[1] || "";
|
|
592
|
-
const timeMatch = block.match(/\|\s*(\d{4}-\d{2}-\d{2}[^|]*)\|/);
|
|
593
|
-
const reviewerMatch = block.match(/Reviewer\s*[::]\s*([^\n]+)/i) || block.match(/审查人\s*[::]\s*([^\n]+)/);
|
|
594
|
-
return {
|
|
595
|
-
confirmed: true,
|
|
596
|
-
confirmedAt: timeMatch ? timeMatch[1].trim() : "",
|
|
597
|
-
reviewer: reviewerMatch ? reviewerMatch[1].trim() : "",
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
export function taskReviewStatus({ reviewContent = "", risks = [], confirmation = null } = {}) {
|
|
602
|
-
if (risks.some(isBlockingReviewRisk)) return "blocked-open-findings";
|
|
603
|
-
if (confirmation?.confirmed) return "confirmed";
|
|
604
|
-
if (!String(reviewContent || "").trim()) return "missing";
|
|
605
|
-
if (/Verdict\s*[::]\s*yes/i.test(reviewContent) || /本轮已检查|未发现阻塞目标的重要发现/.test(reviewContent)) return "reviewed-unconfirmed";
|
|
606
|
-
return "required";
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
export function isBlockingReviewRisk(risk) {
|
|
610
|
-
return /^P[0-2]$/i.test(risk?.severity || "") && (risk.open || risk.blocksRelease);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing" } = {}) {
|
|
614
|
-
if (closeoutStatus === "closed") return "closed";
|
|
615
|
-
if (state === "blocked") return "blocked";
|
|
616
|
-
if (reviewStatus === "blocked-open-findings") return "review-blocked";
|
|
617
|
-
if (state === "done") return "closing";
|
|
618
|
-
if (state === "review") return "in_review";
|
|
619
|
-
if (state === "in_progress") return "active";
|
|
620
|
-
if (["planned", "not_started"].includes(state)) return "ready";
|
|
621
|
-
return "unknown";
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function collectStateConflicts({ state, reviewStatus, closeoutStatus, lifecycleState }) {
|
|
625
|
-
const conflicts = [];
|
|
626
|
-
if (state === "done" && closeoutStatus !== "closed") {
|
|
627
|
-
conflicts.push({
|
|
628
|
-
code: "done-without-closeout",
|
|
629
|
-
severity: "warn",
|
|
630
|
-
message: "Task state is done, but closeout is still missing or pending.",
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
if (reviewStatus === "blocked-open-findings") {
|
|
634
|
-
conflicts.push({
|
|
635
|
-
code: "review-blocked-open-findings",
|
|
636
|
-
severity: "block",
|
|
637
|
-
message: "Open P0-P2 review findings block human review confirmation.",
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
if (lifecycleState === "closed" && reviewStatus === "blocked-open-findings") {
|
|
641
|
-
conflicts.push({
|
|
642
|
-
code: "closed-with-blocking-review",
|
|
643
|
-
severity: "block",
|
|
644
|
-
message: "Closeout is closed while review findings still block release.",
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
return conflicts;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
562
|
function collectHandoffs(progressContent, taskId) {
|
|
651
563
|
if (!/Coordinator Handoff/i.test(progressContent) || !/pending-coordinator-pass/i.test(progressContent)) return [];
|
|
652
564
|
return [{ id: `H-${taskId}`, from: "worker", to: "coordinator", state: "pending", summary: "Coordinator handoff pending" }];
|
|
653
565
|
}
|
|
654
566
|
|
|
655
|
-
export function collectReviewRisks(reviewContent) {
|
|
656
|
-
const { header, rows } = tableAfterHeading(reviewContent, /^ID$/i);
|
|
657
|
-
const severityIndex = getColumn(header, "Severity");
|
|
658
|
-
const findingIndex = getColumn(header, "Finding");
|
|
659
|
-
const openIndex = getColumn(header, "Open");
|
|
660
|
-
const blocksIndex = getColumn(header, "Blocks Release");
|
|
661
|
-
if (severityIndex < 0 || findingIndex < 0) return [];
|
|
662
|
-
return rows
|
|
663
|
-
.filter((row) => /^P[0-3]$/i.test(row[severityIndex] || ""))
|
|
664
|
-
.map((row) => ({
|
|
665
|
-
id: row[0],
|
|
666
|
-
severity: row[severityIndex],
|
|
667
|
-
open: /^yes$/i.test(row[openIndex] || "no"),
|
|
668
|
-
blocksRelease: /^yes$/i.test(row[blocksIndex] || "no"),
|
|
669
|
-
summary: row[findingIndex],
|
|
670
|
-
}));
|
|
671
|
-
}
|
|
672
|
-
|
|
673
567
|
function collectEvidence(progressContent) {
|
|
674
568
|
const matches = [...progressContent.matchAll(/\b(command|diff|fixture|screenshot|review|report):((?:PUBLIC|PRIVATE|TARGET|EXTERNAL|URL):[^:\s|]+):([^\n|]+)/g)];
|
|
675
569
|
return matches.map((match, index) => ({
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
normalizeTarget,
|
|
5
|
+
nowTimestamp,
|
|
6
|
+
readFileSafe,
|
|
7
|
+
toPosix,
|
|
8
|
+
datePrefix,
|
|
9
|
+
} from "./core-shared.mjs";
|
|
10
|
+
import { collectTasks } from "./task-scanner.mjs";
|
|
11
|
+
import {
|
|
12
|
+
beginGovernanceSync,
|
|
13
|
+
commitGovernanceSync,
|
|
14
|
+
releaseGovernanceSync,
|
|
15
|
+
} from "./governance-sync.mjs";
|
|
16
|
+
|
|
17
|
+
export function supersedeTask(targetInput, oldRef, { by = "", reason = "" } = {}) {
|
|
18
|
+
if (!by) throw new Error("task-supersede requires --by <new-task-id>");
|
|
19
|
+
const target = normalizeTarget(targetInput);
|
|
20
|
+
const oldTask = resolveTask(target, oldRef);
|
|
21
|
+
const newTask = resolveTask(target, by);
|
|
22
|
+
const governanceContext = beginGovernanceSync(target, { operation: `task-supersede ${oldTask.id}` });
|
|
23
|
+
try {
|
|
24
|
+
writeTombstone(target, oldTask, {
|
|
25
|
+
State: "superseded",
|
|
26
|
+
"Superseded By": newTask.id,
|
|
27
|
+
Reason: reason || "superseded",
|
|
28
|
+
Operator: "coordinator",
|
|
29
|
+
Timestamp: nowTimestamp(),
|
|
30
|
+
"Reopen Eligible": "yes",
|
|
31
|
+
"Archive Eligible": "no",
|
|
32
|
+
});
|
|
33
|
+
appendProgress(target, oldTask, `task-supersede: superseded by ${newTask.id}`, reason || "superseded");
|
|
34
|
+
appendSupersedes(target, newTask, oldTask.id);
|
|
35
|
+
const commit = commitGovernanceSync(contextFor(target, governanceContext), taskPaths(target, oldTask, newTask), {
|
|
36
|
+
message: `chore(harness): supersede task ${oldTask.id}`,
|
|
37
|
+
});
|
|
38
|
+
return { taskId: oldTask.id, supersededBy: newTask.id, reason: reason || "superseded", governance: { commit } };
|
|
39
|
+
} finally {
|
|
40
|
+
releaseGovernanceSync(governanceContext);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function softDeleteTask(targetInput, taskRef, { reason = "" } = {}) {
|
|
45
|
+
const target = normalizeTarget(targetInput);
|
|
46
|
+
const task = resolveTask(target, taskRef);
|
|
47
|
+
return writeDeletionState(target, task, "soft-deleted", reason || "soft-delete", "task-delete --soft");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function archiveTask(targetInput, taskRef, { reason = "" } = {}) {
|
|
51
|
+
const target = normalizeTarget(targetInput);
|
|
52
|
+
const task = resolveTask(target, taskRef);
|
|
53
|
+
return writeDeletionState(target, task, "archived", reason || "archive", "task-archive");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function reopenTask(targetInput, taskRef, { reason = "" } = {}) {
|
|
57
|
+
const target = normalizeTarget(targetInput);
|
|
58
|
+
const task = resolveTask(target, taskRef);
|
|
59
|
+
const governanceContext = beginGovernanceSync(target, { operation: `task-reopen ${task.id}` });
|
|
60
|
+
try {
|
|
61
|
+
const taskPlanPath = path.join(target.projectRoot, task.taskPlanPath.replace(/^TARGET:/, ""));
|
|
62
|
+
const content = readFileSafe(taskPlanPath);
|
|
63
|
+
const next = content.replace(/\n##\s*(?:Task Tombstone|任务墓碑)\s*$[\s\S]*?(?=^##\s+|(?![\s\S]))/im, "");
|
|
64
|
+
fs.writeFileSync(taskPlanPath, next.endsWith("\n") ? next : `${next}\n`);
|
|
65
|
+
appendProgress(target, task, "task-reopen", reason || "reopened");
|
|
66
|
+
const commit = commitGovernanceSync(governanceContext, taskPaths(target, task), {
|
|
67
|
+
message: `chore(harness): reopen task ${task.id}`,
|
|
68
|
+
});
|
|
69
|
+
return { taskId: task.id, deletionState: "active", reason: reason || "reopened", governance: { commit } };
|
|
70
|
+
} finally {
|
|
71
|
+
releaseGovernanceSync(governanceContext);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function writeDeletionState(target, task, deletionState, reason, action) {
|
|
76
|
+
const governanceContext = beginGovernanceSync(target, { operation: `${action} ${task.id}` });
|
|
77
|
+
try {
|
|
78
|
+
writeTombstone(target, task, {
|
|
79
|
+
State: deletionState,
|
|
80
|
+
Reason: reason,
|
|
81
|
+
Operator: "coordinator",
|
|
82
|
+
Timestamp: nowTimestamp(),
|
|
83
|
+
"Reopen Eligible": "yes",
|
|
84
|
+
"Archive Eligible": deletionState === "archived" ? "yes" : "no",
|
|
85
|
+
});
|
|
86
|
+
appendProgress(target, task, action, reason);
|
|
87
|
+
const commit = commitGovernanceSync(governanceContext, taskPaths(target, task), {
|
|
88
|
+
message: `chore(harness): ${action.replace(/\s+/g, " ")} ${task.id}`,
|
|
89
|
+
});
|
|
90
|
+
return { taskId: task.id, deletionState, reason, governance: { commit } };
|
|
91
|
+
} finally {
|
|
92
|
+
releaseGovernanceSync(governanceContext);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function taskPaths(target, ...tasks) {
|
|
97
|
+
return [...new Set(tasks.flatMap((task) => [task.taskPlanPath, task.progressPath]).filter(Boolean).map((item) => toPosix(item.replace(/^TARGET:/, ""))))];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function contextFor(_target, context) {
|
|
101
|
+
return context;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveTask(target, ref) {
|
|
105
|
+
const normalized = String(ref || "").trim();
|
|
106
|
+
const matches = collectTasks(target).filter((task) => {
|
|
107
|
+
const bare = datePrefix.test(task.shortId) ? task.shortId.replace(datePrefix, "") : task.shortId;
|
|
108
|
+
return task.id === normalized || task.shortId === normalized || task.id.endsWith(`/${normalized}`) || bare === normalized;
|
|
109
|
+
});
|
|
110
|
+
if (matches.length === 1) return matches[0];
|
|
111
|
+
if (matches.length > 1) throw new Error(`Ambiguous task reference: ${ref}`);
|
|
112
|
+
throw new Error(`Task not found: ${ref}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function writeTombstone(target, task, fields) {
|
|
116
|
+
const taskPlanPath = path.join(target.projectRoot, task.taskPlanPath.replace(/^TARGET:/, ""));
|
|
117
|
+
const content = readFileSafe(taskPlanPath).replace(/\n##\s*(?:Task Tombstone|任务墓碑)\s*$[\s\S]*?(?=^##\s+|(?![\s\S]))/im, "");
|
|
118
|
+
const block = ["", "## Task Tombstone", "", "| Field | Value |", "| --- | --- |", ...Object.entries(fields).map(([key, value]) => `| ${key} | ${escapeCell(value)} |`), ""].join("\n");
|
|
119
|
+
fs.writeFileSync(taskPlanPath, `${content.trimEnd()}\n${block}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function appendSupersedes(target, task, oldId) {
|
|
123
|
+
const taskPlanPath = path.join(target.projectRoot, task.taskPlanPath.replace(/^TARGET:/, ""));
|
|
124
|
+
const content = readFileSafe(taskPlanPath);
|
|
125
|
+
if (/^Supersedes\s*[::]/im.test(content)) {
|
|
126
|
+
fs.writeFileSync(taskPlanPath, content.replace(/^Supersedes\s*[::]\s*(.*)$/im, (_m, current) => `Supersedes: ${[current, oldId].filter(Boolean).join(", ")}`));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
fs.writeFileSync(taskPlanPath, `${content.trimEnd()}\nSupersedes: ${oldId}\n`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function appendProgress(target, task, action, reason) {
|
|
133
|
+
const progressPath = path.join(target.projectRoot, task.progressPath.replace(/^TARGET:/, ""));
|
|
134
|
+
const relative = toPosix(path.relative(target.projectRoot, progressPath));
|
|
135
|
+
fs.appendFileSync(progressPath, `\n\n## Tombstone Log\n\n- ${nowTimestamp()} ${action}: ${escapeCell(reason)} (${relative})\n`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function escapeCell(value) {
|
|
139
|
+
return String(value || "").replace(/\r?\n/g, " ").replaceAll("|", "\\|").trim();
|
|
140
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { seedBundledPresets } from "./lib/harness-core.mjs";
|
|
4
|
+
|
|
5
|
+
if (process.env.CODING_AGENT_HARNESS_SKIP_POSTINSTALL === "1") process.exit(0);
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const result = seedBundledPresets({ scope: "user" });
|
|
9
|
+
const changed = result.created + result.overwritten;
|
|
10
|
+
const summary = changed > 0 ? `${changed} bundled presets installed` : `${result.skipped} bundled presets already present`;
|
|
11
|
+
console.log(`coding-agent-harness postinstall: ${summary} at ${result.target}`);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.warn(`coding-agent-harness postinstall: preset seed skipped (${error.message})`);
|
|
14
|
+
}
|