codex-workflow-v2 2.0.0-beta.13.9 → 2.0.0-beta.14.1
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/README.md +27 -388
- package/dist/reviewer-runtime-build.json +98 -34
- package/dist/src/alpha6/adoption.d.ts +3 -1
- package/dist/src/alpha6/adoption.js +48 -8
- package/dist/src/alpha6/adoption.js.map +1 -1
- package/dist/src/alpha6/captured-check-evidence.d.ts +51 -0
- package/dist/src/alpha6/captured-check-evidence.js +152 -0
- package/dist/src/alpha6/captured-check-evidence.js.map +1 -0
- package/dist/src/alpha6/component-owner.d.ts +15 -1
- package/dist/src/alpha6/component-owner.js +44 -2
- package/dist/src/alpha6/component-owner.js.map +1 -1
- package/dist/src/alpha6/corrective-decision-boundary.d.ts +4 -0
- package/dist/src/alpha6/corrective-decision-boundary.js +53 -0
- package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -0
- package/dist/src/alpha6/downstream-proof.d.ts +1 -25
- package/dist/src/alpha6/downstream-proof.js +0 -171
- package/dist/src/alpha6/downstream-proof.js.map +1 -1
- package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
- package/dist/src/alpha6/literal-test-invocation.js +106 -0
- package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
- package/dist/src/alpha6/milestone.d.ts +2 -0
- package/dist/src/alpha6/milestone.js +10 -1
- package/dist/src/alpha6/milestone.js.map +1 -1
- package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js.map +1 -0
- package/dist/src/alpha6/plan-integrity.js +7 -5
- package/dist/src/alpha6/plan-integrity.js.map +1 -1
- package/dist/src/alpha6/remediation.d.ts +8 -21
- package/dist/src/alpha6/remediation.js +225 -474
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
- package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
- package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
- package/dist/src/alpha7/autonomy.d.ts +9 -0
- package/dist/src/alpha7/autonomy.js +40 -1
- package/dist/src/alpha7/autonomy.js.map +1 -1
- package/dist/src/beta1/project-transaction.d.ts +3 -1
- package/dist/src/beta1/project-transaction.js +20 -6
- package/dist/src/beta1/project-transaction.js.map +1 -1
- package/dist/src/checks/runner.d.ts +26 -0
- package/dist/src/checks/runner.js +223 -0
- package/dist/src/checks/runner.js.map +1 -0
- package/dist/src/checks/task-sync.d.ts +10 -0
- package/dist/src/checks/task-sync.js +41 -0
- package/dist/src/checks/task-sync.js.map +1 -0
- package/dist/src/checks/worker.d.ts +1 -0
- package/dist/src/checks/worker.js +154 -0
- package/dist/src/checks/worker.js.map +1 -0
- package/dist/src/cli-actions.d.ts +3 -3
- package/dist/src/cli-actions.js +8 -15
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +43 -97
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +54 -34
- package/dist/src/delegation.d.ts +5 -1
- package/dist/src/delegation.js +4 -0
- package/dist/src/delegation.js.map +1 -1
- package/dist/src/dependency-provenance.d.ts +2 -2
- package/dist/src/dependency-provenance.js +12 -69
- package/dist/src/dependency-provenance.js.map +1 -1
- package/dist/src/domain/base-sync-conflict.d.ts +13 -0
- package/dist/src/domain/base-sync-conflict.js +32 -0
- package/dist/src/domain/base-sync-conflict.js.map +1 -0
- package/dist/src/domain/step-start-admission.d.ts +4 -0
- package/dist/src/domain/step-start-admission.js +9 -0
- package/dist/src/domain/step-start-admission.js.map +1 -0
- package/dist/src/domain/virgin-registration.d.ts +3 -0
- package/dist/src/domain/virgin-registration.js +65 -0
- package/dist/src/domain/virgin-registration.js.map +1 -0
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/failed-checkpoint-update.d.ts +69 -0
- package/dist/src/failed-checkpoint-update.js +171 -0
- package/dist/src/failed-checkpoint-update.js.map +1 -0
- package/dist/src/gateway-handshake.js +0 -8
- package/dist/src/gateway-handshake.js.map +1 -1
- package/dist/src/git.d.ts +3 -1
- package/dist/src/git.js +41 -14
- package/dist/src/git.js.map +1 -1
- package/dist/src/graph.js +25 -3
- package/dist/src/graph.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +2 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.js +45 -9
- package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -1
- package/dist/src/lifecycle/corrective-replan.js +12 -8
- package/dist/src/lifecycle/corrective-replan.js.map +1 -1
- package/dist/src/lifecycle/semantic-registry.js +4 -2
- package/dist/src/lifecycle/semantic-registry.js.map +1 -1
- package/dist/src/navigation-actions.d.ts +9 -0
- package/dist/src/navigation-actions.js +72 -0
- package/dist/src/navigation-actions.js.map +1 -0
- package/dist/src/navigation-update-artifact.d.ts +5 -0
- package/dist/src/navigation-update-artifact.js +227 -0
- package/dist/src/navigation-update-artifact.js.map +1 -0
- package/dist/src/navigation-update.d.ts +39 -0
- package/dist/src/navigation-update.js +82 -0
- package/dist/src/navigation-update.js.map +1 -0
- package/dist/src/observation.js +52 -29
- package/dist/src/observation.js.map +1 -1
- package/dist/src/observed-routes.js +10 -6
- package/dist/src/observed-routes.js.map +1 -1
- package/dist/src/pending-review-update.d.ts +0 -13
- package/dist/src/pending-review-update.js +1 -6
- package/dist/src/pending-review-update.js.map +1 -1
- package/dist/src/reviewer.d.ts +1 -1
- package/dist/src/reviewer.js +20 -17
- package/dist/src/reviewer.js.map +1 -1
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +6 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/state/corrective-replan-executor.d.ts +10 -0
- package/dist/src/state/corrective-replan-executor.js +38 -1
- package/dist/src/state/corrective-replan-executor.js.map +1 -1
- package/dist/src/state/corrective-replan-public-schema.js +15 -2
- package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
- package/dist/src/state/corrective-replan-public.d.ts +9 -1
- package/dist/src/state/corrective-replan-public.js +50 -8
- package/dist/src/state/corrective-replan-public.js.map +1 -1
- package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
- package/dist/src/state/corrective-replan-transaction.js +12 -11
- package/dist/src/state/corrective-replan-transaction.js.map +1 -1
- package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
- package/dist/src/state/corrective-yield-transaction.js +11 -10
- package/dist/src/state/corrective-yield-transaction.js.map +1 -1
- package/dist/src/state/lock.d.ts +4 -0
- package/dist/src/state/lock.js +21 -0
- package/dist/src/state/lock.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-blocker-route.d.ts +8 -0
- package/dist/src/workflow-blocker-route.js +106 -0
- package/dist/src/workflow-blocker-route.js.map +1 -0
- package/dist/src/workflow.d.ts +88 -64
- package/dist/src/workflow.js +832 -1456
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +26 -304
- package/docs/decisions.md +14 -112
- package/docs/development-flow.md +36 -237
- package/docs/project-memory.md +31 -50
- package/docs/release-app-evidence.md +190 -0
- package/docs/release.md +101 -388
- package/docs/updating-existing-project.md +65 -726
- package/package.json +11 -13
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
- package/plugins/codex-workflow-gateway/references/chat-dispatch.md +88 -200
- package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
- package/plugins/codex-workflow-gateway/references/protocol.md +51 -458
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +20 -4
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +21 -34
- package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +92 -778
- package/references/state-machine.md +7 -5
- package/roles/delivery-coordinator.md +31 -11
- package/roles/scope-lead.md +1 -1
- package/roles/technical-planner.md +5 -2
- package/schemas/adoption-posture-event.schema.json +11 -0
- package/schemas/authorization-event.schema.json +1 -1
- package/schemas/corrective-decision-event.schema.json +26 -4
- package/schemas/corrective-replan-credentials.private.schema.json +8 -5
- package/schemas/corrective-replan-execution-event.private.schema.json +9 -0
- package/schemas/corrective-replan-public.schema.json +2 -1
- package/schemas/delegation-grant.schema.json +1 -1
- package/schemas/project-knowledge-map.schema.json +3 -1
- package/schemas/remediation-event.schema.json +10 -1
- package/schemas/task.schema.json +64 -2
- package/schemas/transition-definition.schema.json +4 -0
- package/schemas/transition-payloads.schema.json +16 -1
- package/src/alpha6/adoption.ts +1146 -0
- package/src/alpha6/captured-check-evidence.ts +132 -0
- package/src/alpha6/check-support-anchor.ts +128 -0
- package/src/alpha6/component-owner.ts +168 -0
- package/src/alpha6/corrective-decision-boundary.ts +45 -0
- package/src/alpha6/downstream-proof.ts +520 -0
- package/src/alpha6/failed-step-planning-recovery.ts +88 -0
- package/src/alpha6/handoff.ts +1443 -0
- package/src/alpha6/journal.ts +473 -0
- package/src/alpha6/literal-test-invocation.ts +84 -0
- package/src/alpha6/mechanical-feasibility.ts +488 -0
- package/src/alpha6/milestone.ts +2192 -0
- package/src/alpha6/navigation-dirty-carryover.ts +154 -0
- package/src/alpha6/npm-check-contract.ts +47 -0
- package/src/alpha6/plan-integrity.ts +298 -0
- package/src/alpha6/plan-risk.ts +1480 -0
- package/src/alpha6/preexecution-replan.ts +187 -0
- package/src/alpha6/remediation-cause.ts +98 -0
- package/src/alpha6/remediation.ts +2505 -0
- package/src/alpha6/review.ts +1198 -0
- package/src/alpha6/root-cause-replan-carryover.ts +491 -0
- package/src/alpha6/store-sidecars.ts +335 -0
- package/src/alpha7/autonomy.ts +449 -0
- package/src/alpha7/corrective-recovery.ts +1332 -0
- package/src/artifacts.ts +130 -0
- package/src/beta1/project-transaction.ts +364 -0
- package/src/change-explanation.ts +153 -0
- package/src/checks/runner.ts +245 -0
- package/src/checks/task-sync.ts +41 -0
- package/src/checks/worker.ts +149 -0
- package/src/cli-actions.ts +112 -0
- package/src/cli.ts +1490 -0
- package/src/contracts.ts +1512 -0
- package/src/credential-output.ts +89 -0
- package/src/credential-transport.ts +215 -0
- package/src/delegation.ts +194 -0
- package/src/dependency-provenance.ts +472 -0
- package/src/diagnostics.ts +93 -0
- package/src/domain/base-sync-conflict.ts +35 -0
- package/src/domain/completed-step-carryover.ts +76 -0
- package/src/domain/discovery.ts +27 -0
- package/src/domain/plan-semantics.ts +58 -0
- package/src/domain/step-start-admission.ts +10 -0
- package/src/domain/validation.ts +177 -0
- package/src/domain/virgin-registration.ts +46 -0
- package/src/errors.ts +24 -0
- package/src/failed-checkpoint-update.ts +203 -0
- package/src/fs-utils.ts +61 -0
- package/src/gateway-handshake.ts +95 -0
- package/src/git.ts +183 -0
- package/src/graph.ts +342 -0
- package/src/historical-step-provenance.ts +136 -0
- package/src/index.ts +23 -0
- package/src/lifecycle/canonical-hash.ts +28 -0
- package/src/lifecycle/catalog.ts +202 -0
- package/src/lifecycle/compiler-inspection.ts +29 -0
- package/src/lifecycle/core-static-readiness.ts +132 -0
- package/src/lifecycle/corrective-replan-authority.ts +136 -0
- package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
- package/src/lifecycle/corrective-replan-credential-core.ts +437 -0
- package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
- package/src/lifecycle/corrective-replan-credentials.ts +48 -0
- package/src/lifecycle/corrective-replan.ts +847 -0
- package/src/lifecycle/evaluator.ts +48 -0
- package/src/lifecycle/fingerprint.ts +488 -0
- package/src/lifecycle/immutable.ts +8 -0
- package/src/lifecycle/implementation-table.ts +118 -0
- package/src/lifecycle/index.ts +8 -0
- package/src/lifecycle/schema-artifact.ts +263 -0
- package/src/lifecycle/semantic-registry.ts +574 -0
- package/src/lifecycle/types.ts +838 -0
- package/src/memory.ts +273 -0
- package/src/migration.ts +161 -0
- package/src/navigation-actions.ts +70 -0
- package/src/navigation-update-artifact.ts +198 -0
- package/src/navigation-update.ts +121 -0
- package/src/observation.ts +225 -0
- package/src/observed-routes.ts +665 -0
- package/src/operational-contract.ts +125 -0
- package/src/pending-review-update.ts +175 -0
- package/src/repository.ts +99 -0
- package/src/reviewer.ts +1879 -0
- package/src/runtime.ts +6 -0
- package/src/state/corrective-replan-executor.ts +819 -0
- package/src/state/corrective-replan-public-schema.ts +83 -0
- package/src/state/corrective-replan-public.ts +952 -0
- package/src/state/corrective-replan-transaction.ts +949 -0
- package/src/state/corrective-yield-executor.ts +327 -0
- package/src/state/corrective-yield-transaction.ts +730 -0
- package/src/state/lock.ts +902 -0
- package/src/state/store.ts +567 -0
- package/src/transition-core.ts +330 -0
- package/src/ulid.ts +24 -0
- package/src/version.ts +2 -0
- package/src/workflow-blocker-route.ts +109 -0
- package/src/workflow.ts +10342 -0
- package/docs/alpha7.1-implementation-brief.md +0 -268
- package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
- package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
- package/docs/beta1-stabilization-brief.md +0 -165
- package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
- package/docs/beta13.2-signal-review-recovery.md +0 -38
- package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
- package/docs/change-model.md +0 -118
- package/docs/delegated-approval.md +0 -254
- package/docs/lifecycle/state-machine-stabilization.md +0 -641
- package/docs/pdf/README.md +0 -24
- package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
- package/docs/pdf/requirements.txt +0 -1
- package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -506
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
- package/docs/pending-review-update.md +0 -15
- package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
- package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
- package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
- package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
- package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
- package/docs/problem-briefs/README.md +0 -120
- package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
- package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
- package/docs/split-required-recovery.md +0 -47
- package/docs/stable-release-defect-register.md +0 -730
- package/docs/validation-report.md +0 -182
- package/scripts/generate-pdf-docs.py +0 -524
- package/scripts/run-pdf-docs.mjs +0 -62
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION } from './contracts.js';
|
|
2
|
+
import { PACKAGE_NAME, PACKAGE_VERSION } from './version.js';
|
|
3
|
+
|
|
4
|
+
export interface GatewayHandshake {
|
|
5
|
+
packageName: typeof PACKAGE_NAME;
|
|
6
|
+
packageVersion: typeof PACKAGE_VERSION;
|
|
7
|
+
protocolVersion: typeof PROTOCOL_VERSION;
|
|
8
|
+
stateSchemaVersion: typeof STATE_SCHEMA_VERSION;
|
|
9
|
+
capabilities: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function gatewayHandshake(): GatewayHandshake {
|
|
13
|
+
return {
|
|
14
|
+
packageName: PACKAGE_NAME,
|
|
15
|
+
packageVersion: PACKAGE_VERSION,
|
|
16
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
17
|
+
stateSchemaVersion: STATE_SCHEMA_VERSION,
|
|
18
|
+
capabilities: workflowCapabilities(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function workflowCapabilities(): string[] {
|
|
23
|
+
return [
|
|
24
|
+
'project-memory-v1',
|
|
25
|
+
'graph-binding-v1',
|
|
26
|
+
'local-state-v1',
|
|
27
|
+
'milestone-lifecycle-v1',
|
|
28
|
+
'milestone-human-final-gate-v1',
|
|
29
|
+
'delegated-approval-v1',
|
|
30
|
+
'task-knowledge-rebind-v1',
|
|
31
|
+
'task-context-refresh-v1',
|
|
32
|
+
'corrective-plan-audit-v1',
|
|
33
|
+
'safe-update-preflight-v1',
|
|
34
|
+
'bounded-dependency-provenance-recovery-v1',
|
|
35
|
+
'task-local-dependency-provenance-recovery-v1',
|
|
36
|
+
'root-cause-corrective-replan-dirty-carryover-v1',
|
|
37
|
+
'stop-override-context-rebind-v1',
|
|
38
|
+
'stop-override-context-rebind-chain-v1',
|
|
39
|
+
'stop-override-post-audit-dependency-revision-v1',
|
|
40
|
+
'retained-reviewed-history-recovery-v1',
|
|
41
|
+
'historical-step-provenance-recovery-v1',
|
|
42
|
+
'milestone-progress-projection-v1',
|
|
43
|
+
'bounded-plan-integrity-recovery-v1',
|
|
44
|
+
'check-support-scope-augmentation-v1',
|
|
45
|
+
'historical-check-support-scope-adoption-v1',
|
|
46
|
+
'historical-check-support-dirty-carryover-v1',
|
|
47
|
+
'downstream-proof-predecessor-recovery-v1',
|
|
48
|
+
'active-downstream-proof-dependency-recovery-v1',
|
|
49
|
+
'task-mechanical-feasibility-v1',
|
|
50
|
+
'task-human-gate-lease-release-v1',
|
|
51
|
+
'strict-reviewer-v1',
|
|
52
|
+
'legacy-snapshot-v1',
|
|
53
|
+
'plan-risk-audit-sidecar-v1',
|
|
54
|
+
'step-strict-review-sidecar-v1',
|
|
55
|
+
'step-remediation-circuit-breaker-sidecar-v1',
|
|
56
|
+
'milestone-scope-change-sidecar-v1',
|
|
57
|
+
'milestone-membership-integrity-v1',
|
|
58
|
+
'milestone-task-dependency-dag-v1',
|
|
59
|
+
'milestone-initial-plan-transaction-v1',
|
|
60
|
+
'task-c1-handoff-sidecar-v1',
|
|
61
|
+
'semantic-plan-risk-audit-v2',
|
|
62
|
+
'guarded-remediation-continuation-v1',
|
|
63
|
+
'cause-bound-rolling-refinement-v1',
|
|
64
|
+
'failed-step-continuation-transaction-v1',
|
|
65
|
+
'host-safe-credential-reference-v1',
|
|
66
|
+
'structural-production-edge-contract-v1',
|
|
67
|
+
'structural-production-edge-authority-v1',
|
|
68
|
+
'observed-route-matrix-v2',
|
|
69
|
+
'compact-status-v1',
|
|
70
|
+
'reviewer-runtime-preflight-v1',
|
|
71
|
+
'explicit-review-mode-v1',
|
|
72
|
+
'app-owned-operational-preflight-v1',
|
|
73
|
+
'milestone-reverse-membership-integrity-v1',
|
|
74
|
+
'task-c1-handoff-bundle-v2',
|
|
75
|
+
'alpha6-adoption-posture-v1',
|
|
76
|
+
'milestone-initial-assembly-v1',
|
|
77
|
+
'milestone-initial-assembly-navigation-v2',
|
|
78
|
+
'milestone-autonomy-contract-v1',
|
|
79
|
+
'milestone-autonomy-content-refresh-v1',
|
|
80
|
+
'milestone-autonomous-membership-evolution-v1',
|
|
81
|
+
'audited-remediation-topology-v1',
|
|
82
|
+
'audited-remediation-dependency-knowledge-refresh-v1',
|
|
83
|
+
'corrective-derived-task-authority-v1',
|
|
84
|
+
'task-c1-derived-worker-actor-v1',
|
|
85
|
+
'guarded-corrective-auditor-routing-v1',
|
|
86
|
+
'corrective-decision-context-recovery-v1',
|
|
87
|
+
'plan-proof-obligations-v1',
|
|
88
|
+
'structural-task-replacement-disabled-v1',
|
|
89
|
+
'project-composite-transaction-journal-v1',
|
|
90
|
+
'corrective-yield-journal-v1',
|
|
91
|
+
'corrective-replan-journal-v1',
|
|
92
|
+
'corrective-replan-human-gate-v1',
|
|
93
|
+
'corrective-replan-protocol-v2',
|
|
94
|
+
];
|
|
95
|
+
}
|
package/src/git.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { WorkflowError } from './errors.js';
|
|
3
|
+
import { runCheckCommands, type CheckExecutionOptions } from './checks/runner.js';
|
|
4
|
+
import { changedFiles, currentBranch, headCommit, isClean, runGit } from './repository.js';
|
|
5
|
+
import { pathAllowed } from './domain/validation.js';
|
|
6
|
+
import type { TaskState } from './contracts.js';
|
|
7
|
+
|
|
8
|
+
const PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'staging', 'production']);
|
|
9
|
+
|
|
10
|
+
export function startLocalTaskBranch(
|
|
11
|
+
repositoryRoot: string,
|
|
12
|
+
branch: string,
|
|
13
|
+
baseBranch: string,
|
|
14
|
+
expectedBaseCommit: string | null = null,
|
|
15
|
+
): string {
|
|
16
|
+
if (!isClean(repositoryRoot)) {
|
|
17
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Task start requires a clean checkout.', {
|
|
18
|
+
changedFiles: changedFiles(repositoryRoot),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
if (PROTECTED_BRANCHES.has(branch)) {
|
|
22
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', `Task branch is protected: ${branch}`);
|
|
23
|
+
}
|
|
24
|
+
const baseCommit = runGit(repositoryRoot, ['rev-parse', baseBranch]);
|
|
25
|
+
if (expectedBaseCommit !== null && baseCommit !== expectedBaseCommit) {
|
|
26
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Task start base moved after execution authorization.', {
|
|
27
|
+
baseBranch,
|
|
28
|
+
authorizedBaseCommit: expectedBaseCommit,
|
|
29
|
+
currentBaseCommit: baseCommit,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
runGit(repositoryRoot, ['switch', '-c', branch, baseBranch]);
|
|
33
|
+
return baseCommit;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CheckResult {
|
|
37
|
+
command: string;
|
|
38
|
+
passed: boolean;
|
|
39
|
+
exitCode: number;
|
|
40
|
+
output: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Only ordinary exited checks can enter product failure/remediation evidence. */
|
|
44
|
+
export function runChecks(repositoryRoot: string, commands: string[], options: CheckExecutionOptions = {}): CheckResult[] {
|
|
45
|
+
const configured: CheckExecutionOptions = { ...options };
|
|
46
|
+
const envNumber = (name: string): number | undefined => {
|
|
47
|
+
const value = process.env[name];
|
|
48
|
+
if (value === undefined) return undefined;
|
|
49
|
+
const parsed = Number(value);
|
|
50
|
+
if (!value.trim() || !Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
51
|
+
throw new WorkflowError('INVALID_ARGUMENT', `${name} must be a positive integer.`);
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
};
|
|
55
|
+
if (configured.timeoutMs === undefined) {
|
|
56
|
+
const value = envNumber('CODEX_WORKFLOW_CHECK_TIMEOUT_MS');
|
|
57
|
+
if (value !== undefined) configured.timeoutMs = value;
|
|
58
|
+
}
|
|
59
|
+
if (configured.totalTimeoutMs === undefined) {
|
|
60
|
+
const value = envNumber('CODEX_WORKFLOW_CHECK_TOTAL_TIMEOUT_MS');
|
|
61
|
+
if (value !== undefined) configured.totalTimeoutMs = value;
|
|
62
|
+
}
|
|
63
|
+
if (configured.cancellationFile === undefined && process.env.CODEX_WORKFLOW_CHECK_CANCEL_FILE !== undefined) {
|
|
64
|
+
configured.cancellationFile = process.env.CODEX_WORKFLOW_CHECK_CANCEL_FILE;
|
|
65
|
+
}
|
|
66
|
+
const results = runCheckCommands(repositoryRoot, commands, configured);
|
|
67
|
+
if (results.some(result => result.outcome === 'infra')) {
|
|
68
|
+
throw new WorkflowError('CHECK_INFRASTRUCTURE_FAILED',
|
|
69
|
+
'Check execution ended with an infrastructure failure. Product failure and corrective replanning were not inferred.', {
|
|
70
|
+
checks: results,
|
|
71
|
+
retryRequiresFreshObservation: true,
|
|
72
|
+
automaticRetry: false,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return results.map(result => {
|
|
76
|
+
if (result.exitCode === null || result.signal !== null || result.error !== null || !result.cleanupComplete) {
|
|
77
|
+
throw new WorkflowError('CHECK_INFRASTRUCTURE_FAILED', 'Check result lacks a verified ordinary exit.', { checks: results });
|
|
78
|
+
}
|
|
79
|
+
return { command: result.command, passed: result.outcome === 'passed', exitCode: result.exitCode, output: result.output };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function commitStep(
|
|
84
|
+
repositoryRoot: string,
|
|
85
|
+
taskId: string,
|
|
86
|
+
stepId: string,
|
|
87
|
+
title: string,
|
|
88
|
+
allowedWrites: string[],
|
|
89
|
+
): { commitSha: string; files: string[] } {
|
|
90
|
+
const files = changedFiles(repositoryRoot);
|
|
91
|
+
if (files.length === 0) throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} has no changed files.`);
|
|
92
|
+
const unexpected = files.filter((file) => !pathAllowed(file, allowedWrites));
|
|
93
|
+
if (unexpected.length > 0) {
|
|
94
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} changed files outside allowedWrites.`, { unexpected });
|
|
95
|
+
}
|
|
96
|
+
runGit(repositoryRoot, ['add', '--', ...files]);
|
|
97
|
+
runGit(repositoryRoot, [
|
|
98
|
+
'commit',
|
|
99
|
+
'-m',
|
|
100
|
+
`${taskId}/${stepId}: ${title}`,
|
|
101
|
+
'-m',
|
|
102
|
+
`Task: ${taskId}\nStep: ${stepId}`,
|
|
103
|
+
]);
|
|
104
|
+
return { commitSha: headCommit(repositoryRoot), files };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function mergeTaskBranch(repositoryRoot: string, taskBranch: string, baseBranch: string): string {
|
|
108
|
+
if (!isClean(repositoryRoot)) throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Merge requires a clean checkout.');
|
|
109
|
+
if (currentBranch(repositoryRoot) !== taskBranch) {
|
|
110
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', `Expected current task branch ${taskBranch}.`);
|
|
111
|
+
}
|
|
112
|
+
runGit(repositoryRoot, ['switch', baseBranch]);
|
|
113
|
+
const result = spawnSync('git', ['-C', repositoryRoot, 'merge', '--no-ff', taskBranch, '-m', `${taskBranch}: merge task`], {
|
|
114
|
+
encoding: 'utf8',
|
|
115
|
+
});
|
|
116
|
+
if (result.status !== 0) {
|
|
117
|
+
spawnSync('git', ['-C', repositoryRoot, 'merge', '--abort'], { encoding: 'utf8' });
|
|
118
|
+
runGit(repositoryRoot, ['switch', taskBranch]);
|
|
119
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', `Merge conflict while merging ${taskBranch}.`, {
|
|
120
|
+
stdout: result.stdout.trim(),
|
|
121
|
+
stderr: result.stderr.trim(),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return headCommit(repositoryRoot);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function syncBaseIntoTask(
|
|
128
|
+
repositoryRoot: string,
|
|
129
|
+
taskBranch: string,
|
|
130
|
+
baseBranch: string,
|
|
131
|
+
): { baseCommit: string; syncCommit: string } {
|
|
132
|
+
if (!isClean(repositoryRoot)) throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Base sync requires a clean checkout.');
|
|
133
|
+
if (currentBranch(repositoryRoot) !== taskBranch) {
|
|
134
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', `Expected current task branch ${taskBranch}.`);
|
|
135
|
+
}
|
|
136
|
+
const result = spawnSync('git', ['-C', repositoryRoot, 'merge', '--no-edit', baseBranch], { encoding: 'utf8' });
|
|
137
|
+
if (result.status !== 0) {
|
|
138
|
+
const abort = spawnSync('git', ['-C', repositoryRoot, 'merge', '--abort'], { encoding: 'utf8' });
|
|
139
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', `Base sync conflict from ${baseBranch}.`, {
|
|
140
|
+
cleanAbortVerified: abort.status === 0 && isClean(repositoryRoot),
|
|
141
|
+
stdout: result.stdout.trim(),
|
|
142
|
+
stderr: result.stderr.trim(),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
baseCommit: runGit(repositoryRoot, ['rev-parse', baseBranch]),
|
|
147
|
+
syncCommit: headCommit(repositoryRoot),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function validateTaskHistory(
|
|
152
|
+
repositoryRoot: string,
|
|
153
|
+
task: TaskState,
|
|
154
|
+
historyBoundary = 'HEAD',
|
|
155
|
+
): void {
|
|
156
|
+
if (!task.baseCommit) throw new WorkflowError('STATE_CORRUPT', `Task ${task.id} has no base commit.`);
|
|
157
|
+
const boundaryCommit = runGit(repositoryRoot, ['rev-parse', historyBoundary]);
|
|
158
|
+
const actualOutput = runGit(repositoryRoot, ['rev-list', '--reverse', `${task.baseCommit}..${boundaryCommit}`]);
|
|
159
|
+
const actual = actualOutput ? actualOutput.split('\n') : [];
|
|
160
|
+
const expected = [...new Set([
|
|
161
|
+
...task.steps.flatMap((step) => (step.evidence ? [step.evidence.commitSha] : [])),
|
|
162
|
+
...task.systemCommits,
|
|
163
|
+
...(task.invalidatedStepCommits ?? []),
|
|
164
|
+
...(task.historicalStepProvenanceRecoveries ?? []).map((recovery) => recovery.commitSha),
|
|
165
|
+
...(task.dependencyProvenanceRecoveries ?? []).flatMap((recovery) => recovery.retainedHistoricalCommits ?? []),
|
|
166
|
+
])];
|
|
167
|
+
const missing = expected.filter((commit) => !actual.includes(commit));
|
|
168
|
+
const unexpected = actual.filter((commit) => !expected.includes(commit));
|
|
169
|
+
if (missing.length > 0 || unexpected.length > 0 || actual.length !== expected.length) {
|
|
170
|
+
throw new WorkflowError(
|
|
171
|
+
'GIT_PRECONDITION_FAILED',
|
|
172
|
+
'Task branch history contains commits not recorded by Workflow V2. Workers must leave Step changes uncommitted and let task step-complete create the commit. Stop and request coordinator-led Git recovery; do not create another commit or edit workflow state.',
|
|
173
|
+
{
|
|
174
|
+
missing,
|
|
175
|
+
unexpected,
|
|
176
|
+
baseCommit: task.baseCommit,
|
|
177
|
+
headCommit: headCommit(repositoryRoot),
|
|
178
|
+
historyBoundary: boundaryCommit,
|
|
179
|
+
recovery: 'Restore the pre-commit working changes under coordinator control, then invoke task step-complete.',
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
package/src/graph.ts
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
copyFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
} from 'node:fs';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { STATE_SCHEMA_VERSION, PROTOCOL_VERSION } from './contracts.js';
|
|
16
|
+
import type {
|
|
17
|
+
GraphBinding,
|
|
18
|
+
GraphBindingSnapshot,
|
|
19
|
+
GraphKind,
|
|
20
|
+
GraphMode,
|
|
21
|
+
GraphRefreshEvidence,
|
|
22
|
+
GraphRefreshRequest,
|
|
23
|
+
} from './contracts.js';
|
|
24
|
+
import { WorkflowError } from './errors.js';
|
|
25
|
+
import { writeJsonAtomic } from './fs-utils.js';
|
|
26
|
+
import { headCommit } from './repository.js';
|
|
27
|
+
import { PACKAGE_NAME, PACKAGE_VERSION } from './version.js';
|
|
28
|
+
|
|
29
|
+
export interface GraphManifest {
|
|
30
|
+
packageName: string;
|
|
31
|
+
packageVersion: string;
|
|
32
|
+
protocolVersion: number;
|
|
33
|
+
stateSchemaVersion: number;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
files: Array<{ path: string; sha256: string }>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const WORKFLOW_GRAPH_SOURCE_ROOTS = ['src', 'roles', 'references', 'schemas', 'plugins/codex-workflow-gateway'] as const;
|
|
39
|
+
|
|
40
|
+
export function graphViewRoot(): string {
|
|
41
|
+
const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
42
|
+
return path.join(codexHome, 'workflow-cache', 'codex-workflow', PACKAGE_VERSION);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function prepareGraphView(target = graphViewRoot()): { root: string; manifest: GraphManifest } {
|
|
46
|
+
const packageRoot = resolvePackageRoot();
|
|
47
|
+
const temporary = `${target}.tmp-${randomUUID()}`;
|
|
48
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
49
|
+
mkdirSync(temporary, { recursive: true, mode: 0o700 });
|
|
50
|
+
try {
|
|
51
|
+
for (const relative of ['dist/src', 'roles', 'references', 'schemas']) {
|
|
52
|
+
const source = path.join(packageRoot, relative);
|
|
53
|
+
if (!existsSync(source)) {
|
|
54
|
+
throw new WorkflowError('NOT_FOUND', `Graph source is missing from package: ${relative}`);
|
|
55
|
+
}
|
|
56
|
+
copyTreeWithoutSymlinks(source, path.join(temporary, relative));
|
|
57
|
+
}
|
|
58
|
+
const files = listFiles(temporary).map((file) => ({
|
|
59
|
+
path: path.relative(temporary, file).replaceAll('\\', '/'),
|
|
60
|
+
sha256: createHash('sha256').update(readFileSync(file)).digest('hex'),
|
|
61
|
+
}));
|
|
62
|
+
const manifest: GraphManifest = {
|
|
63
|
+
packageName: PACKAGE_NAME,
|
|
64
|
+
packageVersion: PACKAGE_VERSION,
|
|
65
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
66
|
+
stateSchemaVersion: STATE_SCHEMA_VERSION,
|
|
67
|
+
createdAt: new Date().toISOString(),
|
|
68
|
+
files,
|
|
69
|
+
};
|
|
70
|
+
writeJsonAtomic(path.join(temporary, 'manifest.json'), manifest);
|
|
71
|
+
rmSync(target, { recursive: true, force: true });
|
|
72
|
+
mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
73
|
+
renameSync(temporary, target);
|
|
74
|
+
return { root: target, manifest };
|
|
75
|
+
} finally {
|
|
76
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function inspectGraphView(target = graphViewRoot()): { present: boolean; current: boolean; root: string } {
|
|
81
|
+
const manifestFile = path.join(target, 'manifest.json');
|
|
82
|
+
if (!existsSync(manifestFile)) return { present: false, current: false, root: target };
|
|
83
|
+
try {
|
|
84
|
+
const manifest = JSON.parse(readFileSync(manifestFile, 'utf8')) as GraphManifest;
|
|
85
|
+
return {
|
|
86
|
+
present: true,
|
|
87
|
+
current:
|
|
88
|
+
manifest.packageName === PACKAGE_NAME &&
|
|
89
|
+
manifest.packageVersion === PACKAGE_VERSION &&
|
|
90
|
+
manifest.protocolVersion === PROTOCOL_VERSION &&
|
|
91
|
+
manifest.stateSchemaVersion === STATE_SCHEMA_VERSION,
|
|
92
|
+
root: target,
|
|
93
|
+
};
|
|
94
|
+
} catch {
|
|
95
|
+
return { present: true, current: false, root: target };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function createGraphRefreshRequest(
|
|
100
|
+
repositoryRoot: string,
|
|
101
|
+
projectId: string,
|
|
102
|
+
graphKind: GraphKind,
|
|
103
|
+
mode: GraphMode = 'moderate',
|
|
104
|
+
now: Date = new Date(),
|
|
105
|
+
): GraphRefreshRequest {
|
|
106
|
+
if (graphKind === 'product') {
|
|
107
|
+
const sourceRoot = path.resolve(repositoryRoot);
|
|
108
|
+
return {
|
|
109
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
110
|
+
requestId: randomUUID(),
|
|
111
|
+
projectId,
|
|
112
|
+
graphKind,
|
|
113
|
+
sourceRoot,
|
|
114
|
+
rootFingerprint: createHash('sha256').update(`${projectId}\0${sourceRoot}`).digest('hex'),
|
|
115
|
+
indexedHead: headCommit(sourceRoot),
|
|
116
|
+
packageVersion: null,
|
|
117
|
+
manifestHash: null,
|
|
118
|
+
mode,
|
|
119
|
+
persistence: false,
|
|
120
|
+
issuedAt: now.toISOString(),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const packageRoot = resolvePackageRoot();
|
|
124
|
+
const manifest = workflowPackageGraphManifest(packageRoot, now);
|
|
125
|
+
const manifestHash = hashGraphManifest(manifest);
|
|
126
|
+
return {
|
|
127
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
128
|
+
requestId: randomUUID(),
|
|
129
|
+
projectId,
|
|
130
|
+
graphKind,
|
|
131
|
+
sourceRoot: packageRoot,
|
|
132
|
+
rootFingerprint: createHash('sha256').update(`${PACKAGE_NAME}\0${PACKAGE_VERSION}\0${manifestHash}`).digest('hex'),
|
|
133
|
+
indexedHead: null,
|
|
134
|
+
packageVersion: PACKAGE_VERSION,
|
|
135
|
+
manifestHash,
|
|
136
|
+
mode,
|
|
137
|
+
persistence: false,
|
|
138
|
+
issuedAt: now.toISOString(),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function workflowPackageGraphManifest(packageRoot: string, now: Date): GraphManifest {
|
|
143
|
+
const files = WORKFLOW_GRAPH_SOURCE_ROOTS.flatMap((relative) => {
|
|
144
|
+
const source = path.join(packageRoot, relative);
|
|
145
|
+
if (!existsSync(source)) {
|
|
146
|
+
throw new WorkflowError('NOT_FOUND', `Workflow graph source is missing from package: ${relative}`);
|
|
147
|
+
}
|
|
148
|
+
return listFiles(source).map((file) => ({
|
|
149
|
+
path: path.relative(packageRoot, file).replaceAll('\\', '/'),
|
|
150
|
+
sha256: createHash('sha256').update(readFileSync(file)).digest('hex'),
|
|
151
|
+
}));
|
|
152
|
+
}).sort((left, right) => left.path.localeCompare(right.path));
|
|
153
|
+
return {
|
|
154
|
+
packageName: PACKAGE_NAME,
|
|
155
|
+
packageVersion: PACKAGE_VERSION,
|
|
156
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
157
|
+
stateSchemaVersion: STATE_SCHEMA_VERSION,
|
|
158
|
+
createdAt: now.toISOString(),
|
|
159
|
+
files,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function bindGraphEvidence(
|
|
164
|
+
projectId: string,
|
|
165
|
+
request: GraphRefreshRequest,
|
|
166
|
+
evidence: GraphRefreshEvidence,
|
|
167
|
+
existing: GraphBinding | null,
|
|
168
|
+
now: Date = new Date(),
|
|
169
|
+
): GraphBinding {
|
|
170
|
+
validateGraphEvidence(request, evidence);
|
|
171
|
+
const timestamp = now.toISOString();
|
|
172
|
+
const snapshot: GraphBindingSnapshot = {
|
|
173
|
+
projectKey: evidence.projectKey,
|
|
174
|
+
rootFingerprint: evidence.rootFingerprint,
|
|
175
|
+
indexedHead: evidence.indexedHead,
|
|
176
|
+
packageVersion: evidence.packageVersion,
|
|
177
|
+
manifestHash: evidence.manifestHash,
|
|
178
|
+
mode: evidence.mode,
|
|
179
|
+
status: evidence.status,
|
|
180
|
+
nodeCount: evidence.nodeCount,
|
|
181
|
+
edgeCount: evidence.edgeCount,
|
|
182
|
+
observedAt: evidence.observedAt,
|
|
183
|
+
evidenceHash: createHash('sha256').update(JSON.stringify(evidence)).digest('hex'),
|
|
184
|
+
fallbackReason: null,
|
|
185
|
+
};
|
|
186
|
+
const retainPrevious = request.graphKind === 'workflow' && existing?.packageVersion !== snapshot.packageVersion;
|
|
187
|
+
return {
|
|
188
|
+
schemaVersion: STATE_SCHEMA_VERSION,
|
|
189
|
+
revision: existing?.revision ?? 0,
|
|
190
|
+
id: `GRAPH-${request.graphKind}`,
|
|
191
|
+
projectId,
|
|
192
|
+
createdAt: existing?.createdAt ?? timestamp,
|
|
193
|
+
updatedAt: timestamp,
|
|
194
|
+
kind: 'graph-binding',
|
|
195
|
+
graphKind: request.graphKind,
|
|
196
|
+
provider: 'codebase-memory-mcp',
|
|
197
|
+
...snapshot,
|
|
198
|
+
previous: retainPrevious && existing ? graphSnapshot(existing) : existing?.previous ?? null,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function fallbackGraphBinding(
|
|
203
|
+
projectId: string,
|
|
204
|
+
request: GraphRefreshRequest,
|
|
205
|
+
reason: string,
|
|
206
|
+
existing: GraphBinding | null,
|
|
207
|
+
now: Date = new Date(),
|
|
208
|
+
): GraphBinding {
|
|
209
|
+
if (!reason.trim()) throw new WorkflowError('INVALID_ARGUMENT', 'Graph fallback requires a reason.');
|
|
210
|
+
const timestamp = now.toISOString();
|
|
211
|
+
const evidenceHash = createHash('sha256')
|
|
212
|
+
.update(JSON.stringify({ requestId: request.requestId, reason: reason.trim(), observedAt: timestamp }))
|
|
213
|
+
.digest('hex');
|
|
214
|
+
return {
|
|
215
|
+
schemaVersion: STATE_SCHEMA_VERSION,
|
|
216
|
+
revision: existing?.revision ?? 0,
|
|
217
|
+
id: `GRAPH-${request.graphKind}`,
|
|
218
|
+
projectId,
|
|
219
|
+
createdAt: existing?.createdAt ?? timestamp,
|
|
220
|
+
updatedAt: timestamp,
|
|
221
|
+
kind: 'graph-binding',
|
|
222
|
+
graphKind: request.graphKind,
|
|
223
|
+
provider: 'codebase-memory-mcp',
|
|
224
|
+
projectKey: existing?.projectKey ?? null,
|
|
225
|
+
rootFingerprint: request.rootFingerprint,
|
|
226
|
+
indexedHead: request.indexedHead,
|
|
227
|
+
packageVersion: request.packageVersion,
|
|
228
|
+
manifestHash: request.manifestHash,
|
|
229
|
+
mode: request.mode,
|
|
230
|
+
status: 'fallback',
|
|
231
|
+
nodeCount: existing?.nodeCount ?? null,
|
|
232
|
+
edgeCount: existing?.edgeCount ?? null,
|
|
233
|
+
observedAt: timestamp,
|
|
234
|
+
evidenceHash,
|
|
235
|
+
fallbackReason: reason.trim(),
|
|
236
|
+
previous: existing?.previous ?? null,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function inspectGraphBinding(binding: GraphBinding, request: GraphRefreshRequest): GraphBinding {
|
|
241
|
+
const stale =
|
|
242
|
+
binding.rootFingerprint !== request.rootFingerprint ||
|
|
243
|
+
binding.mode !== request.mode ||
|
|
244
|
+
binding.indexedHead !== request.indexedHead ||
|
|
245
|
+
binding.packageVersion !== request.packageVersion ||
|
|
246
|
+
binding.manifestHash !== request.manifestHash;
|
|
247
|
+
return stale && binding.status === 'ready' ? { ...binding, status: 'stale' } : binding;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function validateGraphRequestCurrent(request: GraphRefreshRequest, current: GraphRefreshRequest): void {
|
|
251
|
+
const fields: Array<keyof GraphRefreshRequest> = [
|
|
252
|
+
'projectId',
|
|
253
|
+
'graphKind',
|
|
254
|
+
'sourceRoot',
|
|
255
|
+
'rootFingerprint',
|
|
256
|
+
'indexedHead',
|
|
257
|
+
'packageVersion',
|
|
258
|
+
'manifestHash',
|
|
259
|
+
'mode',
|
|
260
|
+
'persistence',
|
|
261
|
+
];
|
|
262
|
+
const stale = fields.filter((field) => request[field] !== current[field]);
|
|
263
|
+
if (request.protocolVersion !== PROTOCOL_VERSION || stale.length > 0) {
|
|
264
|
+
throw new WorkflowError('STATE_CONFLICT', 'Graph refresh request is stale or incompatible.', { stale });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function hashGraphManifest(manifest: GraphManifest): string {
|
|
269
|
+
return createHash('sha256').update(JSON.stringify({
|
|
270
|
+
packageName: manifest.packageName,
|
|
271
|
+
packageVersion: manifest.packageVersion,
|
|
272
|
+
protocolVersion: manifest.protocolVersion,
|
|
273
|
+
stateSchemaVersion: manifest.stateSchemaVersion,
|
|
274
|
+
files: manifest.files,
|
|
275
|
+
})).digest('hex');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function validateGraphEvidence(request: GraphRefreshRequest, evidence: GraphRefreshEvidence): void {
|
|
279
|
+
const invalid =
|
|
280
|
+
evidence.requestId !== request.requestId ||
|
|
281
|
+
evidence.provider !== 'codebase-memory-mcp' ||
|
|
282
|
+
evidence.rootFingerprint !== request.rootFingerprint ||
|
|
283
|
+
evidence.indexedHead !== request.indexedHead ||
|
|
284
|
+
evidence.packageVersion !== request.packageVersion ||
|
|
285
|
+
evidence.manifestHash !== request.manifestHash ||
|
|
286
|
+
evidence.mode !== request.mode;
|
|
287
|
+
if (invalid || !evidence.projectKey.trim()) {
|
|
288
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Graph evidence does not match the refresh request.');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function graphSnapshot(binding: GraphBinding): GraphBindingSnapshot {
|
|
293
|
+
return {
|
|
294
|
+
projectKey: binding.projectKey,
|
|
295
|
+
rootFingerprint: binding.rootFingerprint,
|
|
296
|
+
indexedHead: binding.indexedHead,
|
|
297
|
+
packageVersion: binding.packageVersion,
|
|
298
|
+
manifestHash: binding.manifestHash,
|
|
299
|
+
mode: binding.mode,
|
|
300
|
+
status: binding.status,
|
|
301
|
+
nodeCount: binding.nodeCount,
|
|
302
|
+
edgeCount: binding.edgeCount,
|
|
303
|
+
observedAt: binding.observedAt,
|
|
304
|
+
evidenceHash: binding.evidenceHash,
|
|
305
|
+
fallbackReason: binding.fallbackReason,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function resolvePackageRoot(): string {
|
|
310
|
+
let cursor = path.dirname(fileURLToPath(import.meta.url));
|
|
311
|
+
for (let depth = 0; depth < 4; depth += 1) {
|
|
312
|
+
const packageFile = path.join(cursor, 'package.json');
|
|
313
|
+
if (existsSync(packageFile)) {
|
|
314
|
+
const parsed = JSON.parse(readFileSync(packageFile, 'utf8')) as { name?: string };
|
|
315
|
+
if (parsed.name === PACKAGE_NAME) return cursor;
|
|
316
|
+
}
|
|
317
|
+
cursor = path.dirname(cursor);
|
|
318
|
+
}
|
|
319
|
+
throw new WorkflowError('NOT_FOUND', `Cannot resolve installed ${PACKAGE_NAME} package root.`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function copyTreeWithoutSymlinks(source: string, destination: string): void {
|
|
323
|
+
const metadata = lstatSync(source);
|
|
324
|
+
if (metadata.isSymbolicLink()) {
|
|
325
|
+
throw new WorkflowError('STATE_CORRUPT', `Graph source must not be a symlink: ${source}`);
|
|
326
|
+
}
|
|
327
|
+
if (metadata.isDirectory()) {
|
|
328
|
+
mkdirSync(destination, { recursive: true, mode: 0o700 });
|
|
329
|
+
for (const entry of readdirSync(source)) {
|
|
330
|
+
copyTreeWithoutSymlinks(path.join(source, entry), path.join(destination, entry));
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
copyFileSync(source, destination);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function listFiles(root: string): string[] {
|
|
338
|
+
return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
|
|
339
|
+
const target = path.join(root, entry.name);
|
|
340
|
+
return entry.isDirectory() ? listFiles(target) : [target];
|
|
341
|
+
}).sort();
|
|
342
|
+
}
|