@planu/cli 5.7.5 → 5.7.6
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 +75 -0
- package/README.md +3 -3
- package/dist/.planu-build.json +1 -1
- package/dist/cli/commands/package-handoff.js +30 -5
- package/dist/config/compliance-profiles.json +36 -3
- package/dist/config/environment-schema.json +21 -0
- package/dist/config/hook-templates/planu-session-safeguard.sh +22 -15
- package/dist/config/official-sdd-tools.d.ts +1 -1
- package/dist/config/official-sdd-tools.js +11 -24
- package/dist/config/registries/hosts/codex.json +14 -30
- package/dist/config/registries/hosts/opencode.json +1 -6
- package/dist/config/server-instructions.js +0 -21
- package/dist/config/skill-templates/planu-context-assets.md +3 -6
- package/dist/config/skill-templates/planu-implement.md +2 -2
- package/dist/config/skill-templates/planu-multi-teammate-review.md +1 -1
- package/dist/config/skill-templates/planu-release.md +2 -7
- package/dist/config/skill-templates/planu-validate.md +3 -3
- package/dist/config/spec-templates/crud-rest-api/template.json +5 -1
- package/dist/config/spec-templates/file-upload-s3/template.json +5 -1
- package/dist/config/spec-templates/stripe-payments/template.json +5 -1
- package/dist/config/spec-templates/webhook-system/template.json +5 -1
- package/dist/config/subagent-templates/planu-challenger.md +4 -9
- package/dist/config/subagent-templates/planu-spec-implementer.md +2 -4
- package/dist/config/subagent-templates/planu-validator.md +5 -8
- package/dist/config/tool-groups.json +6 -144
- package/dist/engine/ambiguity-scorer.js +40 -1
- package/dist/engine/autopilot/bootstrap.js +1 -1
- package/dist/engine/code-impact-analyzer.js +2 -6
- package/dist/engine/context-orchestrator/index.js +2 -2
- package/dist/engine/convention-scanner/codebase-scanner.js +2 -2
- package/dist/engine/dogfooding/runtime-gap-detector.js +2 -2
- package/dist/engine/evidence-gates/artifact-reader.d.ts +1 -0
- package/dist/engine/evidence-gates/artifact-reader.js +1 -1
- package/dist/engine/evidence-gates/evidence-autofill.d.ts +13 -5
- package/dist/engine/evidence-gates/evidence-autofill.js +106 -43
- package/dist/engine/evidence-gates/lifecycle-gate.js +7 -4
- package/dist/engine/evidence-index/index-builder.js +21 -4
- package/dist/engine/execution/job-runtime.js +1 -1
- package/dist/engine/execution/package-handoff-job-executor.d.ts +22 -0
- package/dist/engine/execution/package-handoff-job-executor.js +490 -0
- package/dist/engine/execution/validate-job-executor.js +20 -8
- package/dist/engine/git/exec-git.d.ts +4 -0
- package/dist/engine/git/exec-git.js +14 -0
- package/dist/engine/handoff-artifacts/implementation-review-reader.js +15 -2
- package/dist/engine/handoff-artifacts/schemas.d.ts +2 -0
- package/dist/engine/handoff-artifacts/schemas.js +1 -0
- package/dist/engine/handoff-format.d.ts +36 -0
- package/dist/engine/handoff-format.js +314 -0
- package/dist/engine/handoff-packager.d.ts +5 -3
- package/dist/engine/handoff-packager.js +89 -32
- package/dist/engine/host-tool-filter.js +20 -49
- package/dist/engine/human-summary.js +14 -3
- package/dist/engine/implementation-contract/common.d.ts +8 -3
- package/dist/engine/implementation-contract/common.js +9 -6
- package/dist/engine/implementation-contract/evaluator.js +4 -6
- package/dist/engine/implementation-contract/renderer.js +18 -23
- package/dist/engine/project-health-checker.js +2 -2
- package/dist/engine/readiness-checker.js +27 -0
- package/dist/engine/reconcile/apply-changes.js +4 -0
- package/dist/engine/reconcile/propagate-mirrors.d.ts +3 -0
- package/dist/engine/reconcile/propagate-mirrors.js +311 -0
- package/dist/engine/self-healing/healer.js +12 -5
- package/dist/engine/self-healing/strategies/lint-fix.d.ts +1 -1
- package/dist/engine/self-healing/strategies/lint-fix.js +4 -1
- package/dist/engine/self-healing/strategies/test-fix.d.ts +4 -3
- package/dist/engine/self-healing/strategies/test-fix.js +32 -14
- package/dist/engine/self-healing/strategies/typescript-fix.d.ts +1 -1
- package/dist/engine/self-healing/strategies/typescript-fix.js +28 -10
- package/dist/engine/session/checkpoint-writer.js +8 -4
- package/dist/engine/session/session-tracker.d.ts +5 -16
- package/dist/engine/session/session-tracker.js +29 -26
- package/dist/engine/session-state/writer.js +3 -11
- package/dist/engine/skill-generator/workflow-skill-generator.d.ts +4 -2
- package/dist/engine/skill-generator/workflow-skill-generator.js +113 -38
- package/dist/engine/spec-format/bdd-parser.d.ts +1 -11
- package/dist/engine/spec-format/bdd-parser.js +40 -11
- package/dist/engine/spec-format/lean-spec-generator.js +30 -10
- package/dist/engine/spec-format/retired-scaffold.d.ts +3 -0
- package/dist/engine/spec-format/retired-scaffold.js +5 -0
- package/dist/engine/spec-format/technical-md-populator.d.ts +1 -0
- package/dist/engine/spec-format/technical-md-populator.js +27 -0
- package/dist/engine/spec-format/unified-spec-builder.d.ts +1 -0
- package/dist/engine/spec-format/unified-spec-builder.js +30 -20
- package/dist/engine/spec-grounding/contract.js +115 -1
- package/dist/engine/spec-quality/generic-output-gate.js +37 -3
- package/dist/engine/technical-enricher/index.js +4 -1
- package/dist/engine/type-safety-gate.js +3 -11
- package/dist/engine/universal-rules/catalog.js +0 -2
- package/dist/engine/universal-rules/rules/planu-workflow.js +1 -1
- package/dist/engine/validation/durable-validation.d.ts +3 -2
- package/dist/engine/validation/durable-validation.js +106 -40
- package/dist/engine/validation/validation-freshness.d.ts +3 -2
- package/dist/engine/validation/validation-freshness.js +22 -16
- package/dist/engine/validation/validation-worktree.js +34 -0
- package/dist/hosts/claude-code/ux/mcp-prompts.js +8 -8
- package/dist/index.js +3 -17
- package/dist/resources/process.js +38 -65
- package/dist/storage/session-state-store.js +2 -0
- package/dist/tools/challenge-spec/scenarios-utils.js +2 -2
- package/dist/tools/configure-checkpoint-policy.js +3 -3
- package/dist/tools/create-spec-helpers.js +2 -5
- package/dist/tools/create-spec.js +21 -4
- package/dist/tools/facilitate.js +3 -6
- package/dist/tools/git/branch-ops.js +16 -4
- package/dist/tools/github-release-handler.js +1 -20
- package/dist/tools/init-project/agents-md-writer.js +6 -6
- package/dist/tools/init-project/claude-md-generator.d.ts +0 -1
- package/dist/tools/init-project/claude-md-generator.js +0 -35
- package/dist/tools/init-project/handler.js +7 -5
- package/dist/tools/init-project/per-client-files-writer.js +5 -5
- package/dist/tools/init-project/planu-workflow-generator.js +2 -13
- package/dist/tools/init-project/portable-index-reconciler.d.ts +2 -1
- package/dist/tools/init-project/portable-index-reconciler.js +7 -2
- package/dist/tools/init-project/rules-generator.d.ts +0 -11
- package/dist/tools/init-project/rules-generator.js +4 -85
- package/dist/tools/init-project/scaffold-writer.d.ts +0 -1
- package/dist/tools/init-project/scaffold-writer.js +1 -12
- package/dist/tools/jobs/handlers.d.ts +0 -6
- package/dist/tools/jobs/handlers.js +13 -40
- package/dist/tools/list-specs.js +6 -21
- package/dist/tools/package-handoff.d.ts +3 -2
- package/dist/tools/package-handoff.js +153 -427
- package/dist/tools/reconcile-session-safeguard-hook.js +22 -15
- package/dist/tools/register-sdd-tools.js +0 -23
- package/dist/tools/register-spec-tools/core-spec-tools.js +18 -27
- package/dist/tools/schemas/index.d.ts +1 -1
- package/dist/tools/schemas/index.js +1 -1
- package/dist/tools/schemas/output-schemas.d.ts +1 -18
- package/dist/tools/schemas/output-schemas.js +1 -11
- package/dist/tools/schemas/package-handoff-output-schema.d.ts +40 -0
- package/dist/tools/schemas/package-handoff-output-schema.js +68 -0
- package/dist/tools/schemas/validate-output-schema.d.ts +1 -2
- package/dist/tools/schemas/validate-output-schema.js +3 -2
- package/dist/tools/session-checkpoint.js +2 -2
- package/dist/tools/skill-registry/index.d.ts +0 -1
- package/dist/tools/skill-registry/index.js +0 -1
- package/dist/tools/sync-spec-state-handler.d.ts +2 -2
- package/dist/tools/sync-spec-state-handler.js +23 -3
- package/dist/tools/tool-registry/core-tools.js +1 -140
- package/dist/tools/tool-registry/group-infra.js +2 -39
- package/dist/tools/tool-registry/group-quality-compliance.js +9 -1
- package/dist/tools/update-status/batch.js +17 -0
- package/dist/tools/update-status/dod-gates.js +14 -1
- package/dist/tools/update-status/done-receipt-verifier.d.ts +22 -0
- package/dist/tools/update-status/done-receipt-verifier.js +56 -26
- package/dist/tools/update-status/evidence-gate.js +10 -21
- package/dist/tools/update-status/file-sync.d.ts +1 -0
- package/dist/tools/update-status/file-sync.js +6 -0
- package/dist/tools/update-status/index.d.ts +6 -0
- package/dist/tools/update-status/index.js +39 -44
- package/dist/tools/update-status-actions.js +2 -7
- package/dist/tools/validate.js +34 -28
- package/dist/transports/oauth-validator.js +12 -1
- package/dist/transports/transport-factory.d.ts +2 -1
- package/dist/transports/transport-factory.js +19 -0
- package/dist/types/common/primitives.d.ts +2 -0
- package/dist/types/durable-job.d.ts +45 -0
- package/dist/types/durable-validation.d.ts +1 -1
- package/dist/types/evidence-autofill.d.ts +13 -1
- package/dist/types/handoff-artifacts.d.ts +2 -1
- package/dist/types/readiness.d.ts +8 -1
- package/dist/types/reconcile.d.ts +6 -0
- package/dist/types/skill-registry.d.ts +26 -1
- package/dist/types/spec/core.d.ts +5 -0
- package/dist/types/spec/inputs.d.ts +2 -2
- package/dist/types/spec-format.d.ts +11 -1
- package/dist/types/spec-grounding.d.ts +12 -0
- package/dist/types/transport.d.ts +1 -0
- package/dist/types/validation-receipt.d.ts +27 -0
- package/package.json +3 -2
- package/planu-plugin.json +13 -26
- package/src/i18n/messages/en.json +1 -1
- package/src/i18n/messages/es.json +1 -1
- package/src/i18n/messages/pt.json +1 -1
- package/dist/engine/skill-generator/conventions-hasher.d.ts +0 -7
- package/dist/engine/skill-generator/conventions-hasher.js +0 -24
- package/dist/engine/universal-rules/rules/agent-teams.d.ts +0 -3
- package/dist/engine/universal-rules/rules/agent-teams.js +0 -63
- package/dist/tools/clarify-requirements/multiple-choice.d.ts +0 -30
- package/dist/tools/clarify-requirements/multiple-choice.js +0 -306
- package/dist/tools/clarify-requirements/questions-context.d.ts +0 -9
- package/dist/tools/clarify-requirements/questions-context.js +0 -74
- package/dist/tools/clarify-requirements/questions.d.ts +0 -16
- package/dist/tools/clarify-requirements/questions.js +0 -96
- package/dist/tools/clarify-requirements.d.ts +0 -4
- package/dist/tools/clarify-requirements.js +0 -314
- package/dist/tools/code-graph-handler.d.ts +0 -6
- package/dist/tools/code-graph-handler.js +0 -72
- package/dist/tools/create-rule.d.ts +0 -4
- package/dist/tools/create-rule.js +0 -107
- package/dist/tools/feedback-handler.d.ts +0 -7
- package/dist/tools/feedback-handler.js +0 -150
- package/dist/tools/semantic-search-handler.d.ts +0 -7
- package/dist/tools/semantic-search-handler.js +0 -71
- package/dist/tools/skill-registry/search.d.ts +0 -11
- package/dist/tools/skill-registry/search.js +0 -159
|
@@ -41,8 +41,8 @@ import { handleCheckSpecAccuracy } from '../check-spec-accuracy.js';
|
|
|
41
41
|
import { handleUpdateRegistry } from '../update-registry.js';
|
|
42
42
|
import { CheckSpecAccuracySchema, UpdateRegistrySchema } from '../schemas/registry.js';
|
|
43
43
|
// ── Skill registry tools (register-skill-registry-tools.ts) ─────────────────
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
44
|
+
import { SkillInstallInputSchema } from '../schemas/skill-registry-schemas.js';
|
|
45
|
+
import { handleSkillInstall } from '../skill-registry/index.js';
|
|
46
46
|
// ── Well-known registry tools (register-well-known-tools.ts) ─────────────────
|
|
47
47
|
import { handlePublishRegistry } from '../publish-registry.js';
|
|
48
48
|
import { handleDiscoverRegistry } from '../discover-registry.js';
|
|
@@ -61,7 +61,6 @@ import { handleGenerateDocsSite } from '../generate-docs-site.js';
|
|
|
61
61
|
// ── Memory config tools (register-memory-config-tools.ts) ────────────────────
|
|
62
62
|
import { handleConfigureMemory, handleMemoryStatus } from '../memory-config-handler.js';
|
|
63
63
|
// ── Code graph tools (SPEC-376 / SPEC-1060) ─────────────────────────────────
|
|
64
|
-
import { handleConfigureCodeGraph, handleCodeGraphStatus } from '../code-graph-handler.js';
|
|
65
64
|
// ── Onboarding tools (register-onboarding-tools.ts) ──────────────────────────
|
|
66
65
|
import { handleStartOnboarding, handleGuidedTour, handleOnboardingChecklist, handleQuickStart, handleOnboardingProgress, } from '../onboarding-handler.js';
|
|
67
66
|
// ── Docs registry tools (register-docs-registry-tools.ts) ────────────────────
|
|
@@ -562,18 +561,6 @@ export function registerInfraGroupTools(server) {
|
|
|
562
561
|
annotations: { title: 'Update Framework Registry', destructiveHint: false },
|
|
563
562
|
}, safeTracked('update_registry', async (args) => handleUpdateRegistry(args)));
|
|
564
563
|
// ── Skill registry tools ───────────────────────────────────────────────────
|
|
565
|
-
server.registerTool('skill_search', {
|
|
566
|
-
description: 'Search for installable Claude skills across multiple registries (Anthropic, AgentSkill.sh, Skills.sh). ' +
|
|
567
|
-
'Filter by technology stack, restrict to a specific registry, or search all at once. ' +
|
|
568
|
-
'Returns ranked results with install commands ready to use.',
|
|
569
|
-
inputSchema: {
|
|
570
|
-
query: SkillSearchInputSchema.shape.query,
|
|
571
|
-
registry: SkillSearchInputSchema.shape.registry,
|
|
572
|
-
stack: SkillSearchInputSchema.shape.stack,
|
|
573
|
-
limit: SkillSearchInputSchema.shape.limit,
|
|
574
|
-
},
|
|
575
|
-
annotations: { title: 'Skill Search', readOnlyHint: true },
|
|
576
|
-
}, safeTracked('skill_search', async (args) => handleSkillSearch(args)));
|
|
577
564
|
server.registerTool('skill_install', {
|
|
578
565
|
description: "Install a skill from a registry into a project's .claude/skills/ directory. " +
|
|
579
566
|
'Detects the source registry from the skill name prefix if not specified. ' +
|
|
@@ -780,30 +767,6 @@ export function registerInfraGroupTools(server) {
|
|
|
780
767
|
projectPath: z.string().describe('Absolute path to the project root directory'),
|
|
781
768
|
},
|
|
782
769
|
}, safeGoverned('memory_status', async (args) => handleMemoryStatus(args)));
|
|
783
|
-
// ── Code graph tools ──────────────────────────────────────────────────────
|
|
784
|
-
server.registerTool('configure_code_graph', {
|
|
785
|
-
description: 'Configure a local code graph MCP provider for the project. Supports Colby CodeGraph, CodeGraphContext, Pathfinder, and Axon. Writes the provider entry to .claude.json.',
|
|
786
|
-
annotations: { title: 'Configure Code Graph', readOnlyHint: false },
|
|
787
|
-
inputSchema: {
|
|
788
|
-
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root'),
|
|
789
|
-
action: z
|
|
790
|
-
.enum([
|
|
791
|
-
'setup-colby-codegraph',
|
|
792
|
-
'setup-codegraph-context',
|
|
793
|
-
'setup-pathfinder',
|
|
794
|
-
'setup-axon',
|
|
795
|
-
'status',
|
|
796
|
-
])
|
|
797
|
-
.describe('setup-colby-codegraph: configure @colbymchenry/codegraph | setup-codegraph-context: configure CodeGraphContext | setup-pathfinder: configure Pathfinder | setup-axon: configure Axon | status: show current provider status'),
|
|
798
|
-
},
|
|
799
|
-
}, safeGoverned('configure_code_graph', async (args) => handleConfigureCodeGraph(args)));
|
|
800
|
-
server.registerTool('code_graph_status', {
|
|
801
|
-
description: 'Returns the current code graph MCP provider status from .claude.json, including the active provider if one is configured.',
|
|
802
|
-
annotations: { title: 'Code Graph Status', readOnlyHint: true },
|
|
803
|
-
inputSchema: {
|
|
804
|
-
projectPath: z.string().min(1).max(4096).describe('Absolute path to the project root'),
|
|
805
|
-
},
|
|
806
|
-
}, safeGoverned('code_graph_status', async (args) => handleCodeGraphStatus(args)));
|
|
807
770
|
// ── Onboarding tools ───────────────────────────────────────────────────────
|
|
808
771
|
server.registerTool('start_onboarding', {
|
|
809
772
|
description: 'Start interactive onboarding for a new project. Generates personalized recommendations based on your role, project type, and team size. Returns a markdown guide with next steps.',
|
|
@@ -42,7 +42,7 @@ import { handleDefineLintRule, handleListLintRules, handleRunSpecLint, handleDel
|
|
|
42
42
|
// ── Readiness (register-readiness-tools.ts) ───────────────────────────────────
|
|
43
43
|
import { handleCheckReadiness } from '../check-readiness.js';
|
|
44
44
|
import { handlePackageHandoff } from '../package-handoff.js';
|
|
45
|
-
import { CheckReadinessOutputSchema, SpecIdSchema } from '../schemas/index.js';
|
|
45
|
+
import { CheckReadinessOutputSchema, SpecIdSchema, PackageHandoffOutputSchema, } from '../schemas/index.js';
|
|
46
46
|
// ── Approval (register-approval-tools.ts) ────────────────────────────────────
|
|
47
47
|
import { handleConfigureApprovalPolicy, handleApproveSpec, handleRequestChanges, handleApprovalStatus, handleIssueReviewerToken, } from '../approval-handler.js';
|
|
48
48
|
// ── SPEC-961: Architecture lint ─────────────────────────────────────────────
|
|
@@ -954,6 +954,14 @@ export function registerQualityComplianceGroupTools(s) {
|
|
|
954
954
|
...projectIdSchema,
|
|
955
955
|
specId: SpecIdSchema.describe('Spec ID to generate handoff package for'),
|
|
956
956
|
},
|
|
957
|
+
outputSchema: PackageHandoffOutputSchema,
|
|
958
|
+
annotations: {
|
|
959
|
+
title: 'Submit Durable Handoff Packaging',
|
|
960
|
+
readOnlyHint: false,
|
|
961
|
+
destructiveHint: false,
|
|
962
|
+
idempotentHint: true,
|
|
963
|
+
openWorldHint: false,
|
|
964
|
+
},
|
|
957
965
|
}, safeGoverned('package_handoff', withProject((args) => handlePackageHandoff(args))));
|
|
958
966
|
// ── Approval ─────────────────────────────────────────────────────────────────
|
|
959
967
|
s.registerTool('configure_approval_policy', {
|
|
@@ -90,6 +90,22 @@ export async function handleUpdateStatusBatch(input) {
|
|
|
90
90
|
},
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
+
if (input.status === 'approved' || input.status === 'implementing') {
|
|
94
|
+
const missingIds = uniqueSpecIds.filter((specId) => input.evidence?.[specId] === undefined);
|
|
95
|
+
if (missingIds.length > 0) {
|
|
96
|
+
const fixHint = `Supply an evidence entry for each of: ${missingIds.join(', ')}.`;
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: `BATCH_EVIDENCE_REQUIRED: Missing model-routing evidence for: ${missingIds.join(', ')}.\nFix: ${fixHint}`,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
isError: true,
|
|
105
|
+
structuredContent: { error: 'BATCH_EVIDENCE_REQUIRED', code: 422, missingIds, fixHint },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
93
109
|
const updated = [];
|
|
94
110
|
const skipped = [];
|
|
95
111
|
const failed = [];
|
|
@@ -98,6 +114,7 @@ export async function handleUpdateStatusBatch(input) {
|
|
|
98
114
|
try {
|
|
99
115
|
for (const specId of uniqueSpecIds) {
|
|
100
116
|
const result = await handleUpdateStatus({
|
|
117
|
+
...input.evidence?.[specId],
|
|
101
118
|
specId,
|
|
102
119
|
status: input.status,
|
|
103
120
|
projectId: resolved.projectId,
|
|
@@ -48,6 +48,7 @@ export const implementationReviewTemplate = lazyTemplate(() => renderSchemaSkele
|
|
|
48
48
|
schema_version: HANDOFF_ARTIFACT_TEMPLATE_SCHEMA_VERSION,
|
|
49
49
|
reviewedAt: HANDOFF_ARTIFACT_TEMPLATE_TIMESTAMP,
|
|
50
50
|
'reviewer.agent': 'planu-implementation-reviewer',
|
|
51
|
+
'reviewer.sessionId': '<your-session-id>',
|
|
51
52
|
},
|
|
52
53
|
}));
|
|
53
54
|
export const validationReportTemplate = lazyTemplate(() => renderSchemaSkeleton(ValidationReportV1Schema, {
|
|
@@ -738,9 +739,21 @@ export async function checkImplementationReviewGate(specId, projectId, _force) {
|
|
|
738
739
|
specId,
|
|
739
740
|
error: 'implementation_review_self_review',
|
|
740
741
|
message: 'The implementation reviewer must be a different identity from the implementer. Self-review is blocked.',
|
|
741
|
-
blockers: [
|
|
742
|
+
blockers: [
|
|
743
|
+
`Implementer and reviewer share the session identity "${verified.implementingActor}" (verified.implementingActor).`,
|
|
744
|
+
],
|
|
742
745
|
fixHint: 'Have a different, independent agent write the implementation review evidence, then retry done.',
|
|
743
746
|
});
|
|
747
|
+
case 'identity_unverifiable':
|
|
748
|
+
return implementationReviewGateError({
|
|
749
|
+
specId,
|
|
750
|
+
error: 'implementation_review_identity_unverifiable',
|
|
751
|
+
message: 'Reviewer independence is unverifiable. reviewer.sessionId is missing, blank, or a placeholder, or the implementing actor could not be resolved from the transition log.',
|
|
752
|
+
blockers: [
|
|
753
|
+
`reviewer.sessionId was ${JSON.stringify(verified.review?.reviewer.sessionId ?? null)}. implementingActor was ${JSON.stringify(verified.implementingActor ?? null)}.`,
|
|
754
|
+
],
|
|
755
|
+
fixHint: 'Have planu-implementation-reviewer record its real session identity in reviewer.sessionId, and have the orchestrator pass a real actor on update_status(implementing), then retry done.',
|
|
756
|
+
});
|
|
744
757
|
case 'unreadable':
|
|
745
758
|
return implementationReviewGateError({
|
|
746
759
|
specId,
|
|
@@ -1,6 +1,28 @@
|
|
|
1
|
+
import { RuntimeDatabase } from '../../storage/runtime-db.js';
|
|
1
2
|
import type { DurableJobRecord } from '../../types/durable-job.js';
|
|
3
|
+
import type { ValidationReceipt, ValidationReceiptBindings, ValidationReceiptVerification } from '../../types/validation-receipt.js';
|
|
4
|
+
import { ValidationReceiptStore } from '../../engine/validation/validation-receipt.js';
|
|
2
5
|
import { type ValidationFreshnessLease } from '../../engine/validation/validation-freshness.js';
|
|
3
6
|
export { doneReceiptBindingsDifferOnlyInEvidence } from '../../engine/validation/durable-validation.js';
|
|
7
|
+
export type CurrentBindingsResolution = {
|
|
8
|
+
ok: true;
|
|
9
|
+
currentBindings: ValidationReceiptBindings;
|
|
10
|
+
verification: ValidationReceiptVerification;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
reason: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function resolveVerifiedCurrentBindings(args: {
|
|
16
|
+
database: RuntimeDatabase;
|
|
17
|
+
store: ValidationReceiptStore;
|
|
18
|
+
identity: {
|
|
19
|
+
projectId: string;
|
|
20
|
+
specId: string;
|
|
21
|
+
projectPath?: string;
|
|
22
|
+
specPath?: string;
|
|
23
|
+
};
|
|
24
|
+
receipt: ValidationReceipt;
|
|
25
|
+
}): Promise<CurrentBindingsResolution>;
|
|
4
26
|
export type DoneReceiptVerifier = (identity: {
|
|
5
27
|
projectId: string;
|
|
6
28
|
specId: string;
|
|
@@ -59,6 +59,58 @@ import { ValidationReceiptStore, LOCAL_VALIDATION_RECEIPT_ISSUER, createValidati
|
|
|
59
59
|
import { beginValidationFreshnessBarrier, bindValidationFreshnessLease, captureValidationFreshnessLease, closeValidationFreshnessLease, } from '../../engine/validation/validation-freshness.js';
|
|
60
60
|
import { computeDurableValidationBindings, doneReceiptBindingsDifferOnlyInEvidence, toValidationReceiptBindings, } from '../../engine/validation/durable-validation.js';
|
|
61
61
|
export { doneReceiptBindingsDifferOnlyInEvidence } from '../../engine/validation/durable-validation.js';
|
|
62
|
+
export async function resolveVerifiedCurrentBindings(args) {
|
|
63
|
+
const { database, store, identity, receipt } = args;
|
|
64
|
+
let currentBindings = await store.readCurrentBindings(identity.projectId, identity.specId);
|
|
65
|
+
if (!currentBindings) {
|
|
66
|
+
if (!identity.projectPath || !identity.specPath) {
|
|
67
|
+
return { ok: false, reason: 'stale-bindings' };
|
|
68
|
+
}
|
|
69
|
+
const projectPath = identity.projectPath;
|
|
70
|
+
const specPath = isAbsolute(identity.specPath)
|
|
71
|
+
? identity.specPath
|
|
72
|
+
: join(projectPath, identity.specPath);
|
|
73
|
+
const observed = await computeDurableValidationBindings({
|
|
74
|
+
canonicalProjectId: identity.projectId,
|
|
75
|
+
specId: identity.specId,
|
|
76
|
+
projectPath,
|
|
77
|
+
specPath,
|
|
78
|
+
});
|
|
79
|
+
const observedBindings = toValidationReceiptBindings(observed.bindings, observed.artifactDigests);
|
|
80
|
+
const isExactMatch = Boolean(receipt.bindings) &&
|
|
81
|
+
JSON.stringify(observedBindings) === JSON.stringify(receipt.bindings);
|
|
82
|
+
if (!isExactMatch &&
|
|
83
|
+
!doneReceiptBindingsDifferOnlyInEvidence(observedBindings, receipt.bindings)) {
|
|
84
|
+
return { ok: false, reason: 'stale-bindings' };
|
|
85
|
+
}
|
|
86
|
+
await store.projectCurrentBindings(observedBindings);
|
|
87
|
+
currentBindings = observedBindings;
|
|
88
|
+
}
|
|
89
|
+
const { authority } = createValidationReceiptAuthority({
|
|
90
|
+
database,
|
|
91
|
+
workspaceId: 'global',
|
|
92
|
+
projectId: identity.projectId,
|
|
93
|
+
issuerId: LOCAL_VALIDATION_RECEIPT_ISSUER,
|
|
94
|
+
});
|
|
95
|
+
let verification = await authority.verifyCurrent({
|
|
96
|
+
...currentBindings,
|
|
97
|
+
projectId: identity.projectId,
|
|
98
|
+
specId: identity.specId,
|
|
99
|
+
});
|
|
100
|
+
const evidenceOnlyDrift = verification.reason === 'stale-bindings' &&
|
|
101
|
+
doneReceiptBindingsDifferOnlyInEvidence(currentBindings, receipt.bindings);
|
|
102
|
+
if (evidenceOnlyDrift) {
|
|
103
|
+
verification = await authority.verifyCurrent({
|
|
104
|
+
...receipt.bindings,
|
|
105
|
+
projectId: identity.projectId,
|
|
106
|
+
specId: identity.specId,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (!verification.valid) {
|
|
110
|
+
return { ok: false, reason: verification.reason ?? 'invalid-receipt' };
|
|
111
|
+
}
|
|
112
|
+
return { ok: true, currentBindings, verification };
|
|
113
|
+
}
|
|
62
114
|
export function completedJobPublishesReceipt(job, receiptId) {
|
|
63
115
|
if (job?.state !== 'completed' || !job.result || typeof job.result !== 'object') {
|
|
64
116
|
return false;
|
|
@@ -83,33 +135,11 @@ export async function verifyCurrentDoneReceipt(identity) {
|
|
|
83
135
|
if (receipt.issuer !== LOCAL_VALIDATION_RECEIPT_ISSUER) {
|
|
84
136
|
return { valid: false, reason: 'unknown-issuer' };
|
|
85
137
|
}
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
88
|
-
return { valid: false, reason:
|
|
89
|
-
}
|
|
90
|
-
const { authority } = createValidationReceiptAuthority({
|
|
91
|
-
database,
|
|
92
|
-
workspaceId: 'global',
|
|
93
|
-
projectId: identity.projectId,
|
|
94
|
-
issuerId: LOCAL_VALIDATION_RECEIPT_ISSUER,
|
|
95
|
-
});
|
|
96
|
-
let verification = await authority.verifyCurrent({
|
|
97
|
-
...currentBindings,
|
|
98
|
-
projectId: identity.projectId,
|
|
99
|
-
specId: identity.specId,
|
|
100
|
-
});
|
|
101
|
-
const evidenceOnlyDrift = verification.reason === 'stale-bindings' &&
|
|
102
|
-
doneReceiptBindingsDifferOnlyInEvidence(currentBindings, receipt.bindings);
|
|
103
|
-
if (evidenceOnlyDrift) {
|
|
104
|
-
verification = await authority.verifyCurrent({
|
|
105
|
-
...receipt.bindings,
|
|
106
|
-
projectId: identity.projectId,
|
|
107
|
-
specId: identity.specId,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
if (!verification.valid) {
|
|
111
|
-
return verification;
|
|
138
|
+
const resolution = await resolveVerifiedCurrentBindings({ database, store, identity, receipt });
|
|
139
|
+
if (!resolution.ok) {
|
|
140
|
+
return { valid: false, reason: resolution.reason };
|
|
112
141
|
}
|
|
142
|
+
const { currentBindings, verification } = resolution;
|
|
113
143
|
const receiptJobExists = database
|
|
114
144
|
.listJobs(identity.projectId, 'global')
|
|
115
145
|
.some((job) => completedJobPublishesReceipt(job, receipt.receiptId));
|
|
@@ -63,12 +63,12 @@ import { extractAcceptanceCriteriaTexts } from '../../engine/spec-format/accepta
|
|
|
63
63
|
import { formatKeyValue } from '../output-formatter.js';
|
|
64
64
|
import { RuntimeDatabase } from '../../storage/runtime-db.js';
|
|
65
65
|
import { resolveStorageLayout } from '../../storage/storage-layout.js';
|
|
66
|
-
import { ValidationReceiptStore, LOCAL_VALIDATION_RECEIPT_ISSUER,
|
|
66
|
+
import { ValidationReceiptStore, LOCAL_VALIDATION_RECEIPT_ISSUER, readCurrentValidationReceipt, } from '../../engine/validation/validation-receipt.js';
|
|
67
67
|
import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
|
|
68
68
|
import { captureValidationFreshnessLease } from '../../engine/validation/validation-freshness.js';
|
|
69
69
|
import { computeDurableValidationBindings, toValidationReceiptBindings, } from '../../engine/validation/durable-validation.js';
|
|
70
70
|
import { checkReconciliationFreshness } from '../../engine/evidence-gates/reconciliation-freshness.js';
|
|
71
|
-
import { doneReceiptBindingsDifferOnlyInEvidence } from './done-receipt-verifier.js';
|
|
71
|
+
import { doneReceiptBindingsDifferOnlyInEvidence, resolveVerifiedCurrentBindings, } from './done-receipt-verifier.js';
|
|
72
72
|
import { extractCanonicalFileOwnership } from '../../engine/handoff-packager.js';
|
|
73
73
|
import { generateTaskPlanSkeleton, writeEvidenceSkeleton, } from '../../engine/evidence-gates/evidence-skeletons.js';
|
|
74
74
|
import { extractSection } from '../../engine/spec-format/markdown-sections.js';
|
|
@@ -127,10 +127,13 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
|
|
|
127
127
|
})
|
|
128
128
|
: null;
|
|
129
129
|
if (receiptResult && !receiptResult.ok) {
|
|
130
|
+
const message = receiptResult.reason === 'stale-bindings'
|
|
131
|
+
? 'stale-bindings: validation inputs changed after the receipt was issued. Re-run validate, then retry done.'
|
|
132
|
+
: receiptResult.reason;
|
|
130
133
|
return evidenceGateError({
|
|
131
134
|
specId: args.specId,
|
|
132
135
|
transition: args.transition,
|
|
133
|
-
issues: [{ code: 'evidence_provenance_invalid', message
|
|
136
|
+
issues: [{ code: 'evidence_provenance_invalid', message }],
|
|
134
137
|
requiredContractKinds: [],
|
|
135
138
|
});
|
|
136
139
|
}
|
|
@@ -244,25 +247,11 @@ async function readVerifiedCurrentReceipt(identity) {
|
|
|
244
247
|
if (receipt.issuer !== LOCAL_VALIDATION_RECEIPT_ISSUER) {
|
|
245
248
|
return { ok: false, reason: 'unknown-issuer' };
|
|
246
249
|
}
|
|
247
|
-
const
|
|
248
|
-
if (!
|
|
249
|
-
return { ok: false, reason:
|
|
250
|
-
}
|
|
251
|
-
const { authority } = createValidationReceiptAuthority({
|
|
252
|
-
database,
|
|
253
|
-
workspaceId: 'global',
|
|
254
|
-
projectId: identity.projectId,
|
|
255
|
-
issuerId: LOCAL_VALIDATION_RECEIPT_ISSUER,
|
|
256
|
-
});
|
|
257
|
-
const verification = await authority.verifyCurrent({
|
|
258
|
-
...currentBindings,
|
|
259
|
-
projectId: identity.projectId,
|
|
260
|
-
specId: identity.specId,
|
|
261
|
-
});
|
|
262
|
-
if (!verification.valid) {
|
|
263
|
-
return { ok: false, reason: verification.reason ?? 'invalid-receipt' };
|
|
250
|
+
const resolution = await resolveVerifiedCurrentBindings({ database, store, identity, receipt });
|
|
251
|
+
if (!resolution.ok) {
|
|
252
|
+
return { ok: false, reason: resolution.reason };
|
|
264
253
|
}
|
|
265
|
-
if (!(await currentBindingsAreFresh(identity, currentBindings))) {
|
|
254
|
+
if (!(await currentBindingsAreFresh(identity, resolution.currentBindings))) {
|
|
266
255
|
return { ok: false, reason: 'stale-bindings' };
|
|
267
256
|
}
|
|
268
257
|
return { ok: true, receipt };
|
|
@@ -13,6 +13,7 @@ export interface FrontmatterSyncWarning {
|
|
|
13
13
|
export declare function syncSpecFiles(updatedSpec: Spec, _currentStatus: SpecStatus, newStatus: SpecStatus, projectPath?: string, qualityWarnings?: readonly string[]): Promise<{
|
|
14
14
|
warning?: FrontmatterSyncWarning;
|
|
15
15
|
title?: string;
|
|
16
|
+
driftReconciled?: boolean;
|
|
16
17
|
}>;
|
|
17
18
|
export declare function tryReconcile(newStatus: string, specId: string, projectId: string): Promise<string | null>;
|
|
18
19
|
/**
|
|
@@ -131,6 +131,7 @@ function assertSpecBodyIntegrity(args) {
|
|
|
131
131
|
throw new Error(`SPEC_INTEGRITY_WRITE_FAILED: refusing to write ${args.specId} because update_status(${args.newStatus}) would shrink spec.md body by ${String(lost)} characters without an explicit migration marker.`);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
const FROZEN_TERMINAL_STATUSES = ['done', 'discarded'];
|
|
134
135
|
export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, projectPath, qualityWarnings = []) {
|
|
135
136
|
let warning;
|
|
136
137
|
let refreshedTitle;
|
|
@@ -148,6 +149,11 @@ export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, proj
|
|
|
148
149
|
parsedMetadata.title !== updatedSpec.title) {
|
|
149
150
|
refreshedTitle = parsedMetadata.title;
|
|
150
151
|
}
|
|
152
|
+
if (FROZEN_TERMINAL_STATUSES.includes(newStatus) && parsedMetadata.status === newStatus) {
|
|
153
|
+
return refreshedTitle
|
|
154
|
+
? { title: refreshedTitle, driftReconciled: true }
|
|
155
|
+
: { driftReconciled: true };
|
|
156
|
+
}
|
|
151
157
|
const { updateFrontmatterField } = await import('../../engine/frontmatter-parser.js');
|
|
152
158
|
let updatedContent = updateFrontmatterField(specContent, 'status', newStatus);
|
|
153
159
|
if (qualityWarnings.length > 0) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import type { UpdateStatusInput, ToolResult, Actuals, Spec, ReconciliationInvocationContext } from '../../types/index.js';
|
|
3
|
+
import { type FrontmatterSyncWarning } from './file-sync.js';
|
|
3
4
|
import { type GapScore } from '../../engine/code-scanner/index.js';
|
|
4
5
|
type LifecycleAuthorityRoute = {
|
|
5
6
|
route: 'ordinary';
|
|
@@ -27,6 +28,11 @@ export declare function synthesizeActuals(actuals: Partial<Actuals> | undefined,
|
|
|
27
28
|
export declare function computeTransitionRootBinding(projectPath: string): string;
|
|
28
29
|
/** Recover durable post-commit work for every registered project after server startup. */
|
|
29
30
|
export declare function recoverPendingPostCommitTasksAtStartup(): Promise<void>;
|
|
31
|
+
export declare function resolveTransitionEventAudit(driftReconciled: boolean, baseReason: string | undefined, baseMeta: Record<string, unknown>): {
|
|
32
|
+
reason: string | undefined;
|
|
33
|
+
meta: Record<string, unknown>;
|
|
34
|
+
};
|
|
35
|
+
export declare function buildDriftAppendFailureWarning(specId: string, specPath: string | null, error: unknown): FrontmatterSyncWarning;
|
|
30
36
|
export declare function handleUpdateStatus(params: UpdateStatusInput, server?: McpServer): Promise<ToolResult>;
|
|
31
37
|
export {};
|
|
32
38
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -140,17 +140,6 @@ function buildAutoAdvancePlan(currentStatus, targetStatus) {
|
|
|
140
140
|
stepsExecuted,
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
|
-
/**
|
|
144
|
-
* SPEC-1356 (T-A3): auto-generate traceability-matrix.json when it is missing,
|
|
145
|
-
* BEFORE the evidence gate reads it for a 'done' transition. Never overwrites an
|
|
146
|
-
* existing artifact. Tolerates every internal failure (falls through to the
|
|
147
|
-
* ordinary evidence gate, which will report the real missing-evidence error).
|
|
148
|
-
*
|
|
149
|
-
* When autofill actually writes a fresh file, the newly-created evidence has not
|
|
150
|
-
* been bound by validate's durable receipt yet, so this blocks once with a clear
|
|
151
|
-
* "re-run validate" message instead of letting a stale-relative-to-receipt
|
|
152
|
-
* traceability matrix slide through the done gates silently.
|
|
153
|
-
*/
|
|
154
143
|
async function runTraceabilityAutofillGate(args) {
|
|
155
144
|
try {
|
|
156
145
|
const specPath = isAbsolute(args.spec.specPath) || !args.projectPath
|
|
@@ -158,7 +147,7 @@ async function runTraceabilityAutofillGate(args) {
|
|
|
158
147
|
: join(args.projectPath, args.spec.specPath);
|
|
159
148
|
const specBody = await readSpecContent(specPath);
|
|
160
149
|
if (specBody === null) {
|
|
161
|
-
return null;
|
|
150
|
+
return { warning: null };
|
|
162
151
|
}
|
|
163
152
|
const result = await autofillTraceabilityMatrix({
|
|
164
153
|
projectId: args.projectId,
|
|
@@ -168,29 +157,16 @@ async function runTraceabilityAutofillGate(args) {
|
|
|
168
157
|
ownSpecMdPath: toRepoRelativePath(args.spec.specPath, args.projectPath),
|
|
169
158
|
});
|
|
170
159
|
if (!result.written) {
|
|
171
|
-
return null;
|
|
160
|
+
return { warning: null };
|
|
172
161
|
}
|
|
173
162
|
return {
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
type: 'text',
|
|
177
|
-
text: `traceability-matrix.json was auto-generated at ${result.path} (${String(result.rowCount)} rows). ` +
|
|
178
|
-
'Run validate to bind this evidence, then retry update_status(done).',
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
isError: true,
|
|
182
|
-
structuredContent: {
|
|
183
|
-
error: 'traceability_matrix_autofilled',
|
|
184
|
-
code: 422,
|
|
185
|
-
context: { specId: args.specId, path: result.path, rowCount: result.rowCount },
|
|
186
|
-
fixHint: 'Run validate so the new traceability-matrix.json is bound, then retry done.',
|
|
187
|
-
},
|
|
163
|
+
warning: `traceability-matrix.json auto-generated (${String(result.rowCount)} rows) and bound via evidence-only rebind`,
|
|
188
164
|
};
|
|
189
165
|
}
|
|
190
166
|
catch (error) {
|
|
191
167
|
// reliability-optional: EVIDENCE_AUTOFILL_GATE — falls through to the ordinary evidence gate
|
|
192
168
|
reportClassifiedDegradation('EVIDENCE_AUTOFILL_GATE', error);
|
|
193
|
-
return null;
|
|
169
|
+
return { warning: null };
|
|
194
170
|
}
|
|
195
171
|
}
|
|
196
172
|
/**
|
|
@@ -603,6 +579,18 @@ function buildPersistenceFailureResponse(args) {
|
|
|
603
579
|
},
|
|
604
580
|
};
|
|
605
581
|
}
|
|
582
|
+
export function resolveTransitionEventAudit(driftReconciled, baseReason, baseMeta) {
|
|
583
|
+
return driftReconciled
|
|
584
|
+
? { reason: 'drift-reconciliation', meta: { ...baseMeta, driftReconciled: true } }
|
|
585
|
+
: { reason: baseReason, meta: baseMeta };
|
|
586
|
+
}
|
|
587
|
+
export function buildDriftAppendFailureWarning(specId, specPath, error) {
|
|
588
|
+
return {
|
|
589
|
+
specId,
|
|
590
|
+
specPath,
|
|
591
|
+
reason: `drift-reconciliation audit marker not recorded: ${error instanceof Error ? error.message : String(error)}`,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
606
594
|
async function commitTransitionWithNarrowLock(args) {
|
|
607
595
|
return withApprovalSpecLock(args.projectPath, args.specId, async () => {
|
|
608
596
|
const lock = await acquireLock(args.projectPath, args.specId, {
|
|
@@ -671,7 +659,12 @@ async function commitTransitionWithNarrowLock(args) {
|
|
|
671
659
|
updatedSpec.title = syncResult.title;
|
|
672
660
|
await specStore.updateSpec(args.projectId, args.specId, { title: syncResult.title });
|
|
673
661
|
}
|
|
674
|
-
return {
|
|
662
|
+
return {
|
|
663
|
+
ok: true,
|
|
664
|
+
transitionRecord,
|
|
665
|
+
updatedSpec,
|
|
666
|
+
driftReconciled: syncResult.driftReconciled === true,
|
|
667
|
+
};
|
|
675
668
|
}
|
|
676
669
|
try {
|
|
677
670
|
await rollbackTransitionSpec(transitionRecord);
|
|
@@ -1011,14 +1004,7 @@ export async function handleUpdateStatus(params, server) {
|
|
|
1011
1004
|
sddRoutingGate.gateResults.sddModelRouting = 'forced';
|
|
1012
1005
|
}
|
|
1013
1006
|
}
|
|
1014
|
-
|
|
1015
|
-
// evidence gate below reads it, so a spec never fails 'done' purely
|
|
1016
|
-
// because nobody hand-wrote the traceability artifact. Never overwrites
|
|
1017
|
-
// an existing file. Freshness caveat: when autofill actually writes a
|
|
1018
|
-
// new file, its evidence has not yet been bound by validate's durable
|
|
1019
|
-
// receipt, so we block once and ask the caller to re-run validate
|
|
1020
|
-
// rather than risk certifying stale bindings. See evidence-autofill.ts
|
|
1021
|
-
// for the full rationale.
|
|
1007
|
+
let traceabilityAutofillWarning = null;
|
|
1022
1008
|
if (plannedStatuses.includes('done') &&
|
|
1023
1009
|
!shouldSkipGateForExplicitLegacyHarness('evidence')) {
|
|
1024
1010
|
const autofillGate = await runTraceabilityAutofillGate({
|
|
@@ -1027,9 +1013,7 @@ export async function handleUpdateStatus(params, server) {
|
|
|
1027
1013
|
projectId,
|
|
1028
1014
|
projectPath: effectiveGatePath,
|
|
1029
1015
|
});
|
|
1030
|
-
|
|
1031
|
-
return autofillGate;
|
|
1032
|
-
}
|
|
1016
|
+
traceabilityAutofillWarning = autofillGate.warning;
|
|
1033
1017
|
}
|
|
1034
1018
|
// SPEC-1054: BDD/SDD evidence gates. Non-trivial specs must carry
|
|
1035
1019
|
// Discovery before approval, task-plan before implementation, and
|
|
@@ -1327,7 +1311,9 @@ export async function handleUpdateStatus(params, server) {
|
|
|
1327
1311
|
: undefined;
|
|
1328
1312
|
const qualityWarningsToWrite = newStatus === 'approved'
|
|
1329
1313
|
? [...readinessGate.qualityWarnings, ...formatGate.qualityWarnings]
|
|
1330
|
-
:
|
|
1314
|
+
: newStatus === 'done' && traceabilityAutofillWarning
|
|
1315
|
+
? [...new Set([...(spec.qualityWarnings ?? []), traceabilityAutofillWarning])]
|
|
1316
|
+
: [];
|
|
1331
1317
|
const persistence = await commitTransitionWithNarrowLock({
|
|
1332
1318
|
projectPath: effectiveGatePath ?? projectId,
|
|
1333
1319
|
projectId,
|
|
@@ -1351,7 +1337,7 @@ export async function handleUpdateStatus(params, server) {
|
|
|
1351
1337
|
if (!persistence.ok) {
|
|
1352
1338
|
return persistence.error;
|
|
1353
1339
|
}
|
|
1354
|
-
const { transitionRecord, updatedSpec } = persistence;
|
|
1340
|
+
const { transitionRecord, updatedSpec, driftReconciled } = persistence;
|
|
1355
1341
|
if (updatedSpec.title && updatedSpec.title !== spec.title) {
|
|
1356
1342
|
spec.title = updatedSpec.title;
|
|
1357
1343
|
}
|
|
@@ -1361,6 +1347,11 @@ export async function handleUpdateStatus(params, server) {
|
|
|
1361
1347
|
if (newStatus === 'done') {
|
|
1362
1348
|
await runTransitionMaintenance(transitionProjectPath);
|
|
1363
1349
|
}
|
|
1350
|
+
const transitionEventAudit = resolveTransitionEventAudit(driftReconciled, params.reason, {
|
|
1351
|
+
...transitionEvidenceMeta,
|
|
1352
|
+
transitionId,
|
|
1353
|
+
committedAt,
|
|
1354
|
+
});
|
|
1364
1355
|
await appendTransitionEvent({
|
|
1365
1356
|
projectId,
|
|
1366
1357
|
specId,
|
|
@@ -1368,14 +1359,17 @@ export async function handleUpdateStatus(params, server) {
|
|
|
1368
1359
|
from: originalStatus,
|
|
1369
1360
|
to: newStatus,
|
|
1370
1361
|
actor,
|
|
1371
|
-
reason:
|
|
1362
|
+
reason: transitionEventAudit.reason,
|
|
1372
1363
|
sessionId: params.sessionId,
|
|
1373
1364
|
modelId: params.modelId,
|
|
1374
1365
|
gateResults: sddRoutingGate.gateResults,
|
|
1375
|
-
meta:
|
|
1366
|
+
meta: transitionEventAudit.meta,
|
|
1376
1367
|
}).catch((error) => {
|
|
1377
1368
|
/* reliability-optional: SDD_ROUTING_REPORT — never block committed acknowledgement */
|
|
1378
1369
|
reportClassifiedDegradation('SDD_ROUTING_REPORT', error);
|
|
1370
|
+
if (driftReconciled) {
|
|
1371
|
+
frontmatterSyncWarnings.push(buildDriftAppendFailureWarning(specId, updatedSpec.specPath, error));
|
|
1372
|
+
}
|
|
1379
1373
|
});
|
|
1380
1374
|
if (reverseTransition && params.reason) {
|
|
1381
1375
|
await appendTransitionEvent({
|
|
@@ -1536,6 +1530,7 @@ export async function handleUpdateStatus(params, server) {
|
|
|
1536
1530
|
depGuard: depGuardResult,
|
|
1537
1531
|
// SPEC-698: surface frontmatter sync failures in the response
|
|
1538
1532
|
frontmatterSyncWarnings: frontmatterSyncWarnings.length > 0 ? frontmatterSyncWarnings : null,
|
|
1533
|
+
qualityWarnings: qualityWarningsToWrite.length > 0 ? qualityWarningsToWrite : null,
|
|
1539
1534
|
updatedAt: updatedSpec.updatedAt,
|
|
1540
1535
|
message: ti('tools.update_status.success', { specId, status: newStatus }),
|
|
1541
1536
|
...(collector.hasEntries() ? { autopilotSummary: collector.getMessages() } : {}),
|
|
@@ -71,13 +71,8 @@ export async function runImplementingSideEffects(projectId, specId, transitionPr
|
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
73
|
if (!branchResult.isError) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
autoBranch = branchData.branchName;
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
// ignore parse error
|
|
80
|
-
}
|
|
74
|
+
const branchName = branchResult.structuredContent?.branchName;
|
|
75
|
+
autoBranch = typeof branchName === 'string' ? branchName : undefined;
|
|
81
76
|
}
|
|
82
77
|
}
|
|
83
78
|
// -------------------------------------------------------------------------
|