@lsctech/polaris 0.1.0
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 +87 -0
- package/dist/__mocks__/tool-server-linear.js +34 -0
- package/dist/cli/adopt-approve.js +60 -0
- package/dist/cli/adopt-cognition.js +199 -0
- package/dist/cli/adopt-instructions.js +143 -0
- package/dist/cli/adopt-rules.js +169 -0
- package/dist/cli/adopt-scan.js +25 -0
- package/dist/cli/adopt-smartdocs.js +192 -0
- package/dist/cli/adoption-inventory.js +350 -0
- package/dist/cli/adoption-plan.js +222 -0
- package/dist/cli/args.js +28 -0
- package/dist/cli/args.test.js +30 -0
- package/dist/cli/branding.js +41 -0
- package/dist/cli/graph.js +358 -0
- package/dist/cli/index.js +127 -0
- package/dist/cli/index.test.js +162 -0
- package/dist/cli/init-detect.js +61 -0
- package/dist/cli/init.js +492 -0
- package/dist/cli/init.test.js +259 -0
- package/dist/cli/librarian.js +32 -0
- package/dist/cli/medic.js +96 -0
- package/dist/cli/startup.test.js +23 -0
- package/dist/cli/tracker.js +130 -0
- package/dist/cli/tracker.test.js +126 -0
- package/dist/cli/version.js +10 -0
- package/dist/cli/version.test.js +11 -0
- package/dist/cli/worker.js +287 -0
- package/dist/cluster-state/fixtures/test-clusters.json +19 -0
- package/dist/cluster-state/store.js +302 -0
- package/dist/cluster-state/store.test.js +145 -0
- package/dist/cluster-state/types.js +2 -0
- package/dist/cognition/archive.js +158 -0
- package/dist/cognition/closeout-librarian-types.js +161 -0
- package/dist/cognition/cognition.test.js +623 -0
- package/dist/cognition/index.js +41 -0
- package/dist/cognition/librarian-dispatch.js +391 -0
- package/dist/cognition/librarian-dispatch.test.js +247 -0
- package/dist/cognition/librarian-packet.js +313 -0
- package/dist/cognition/librarian-types.js +57 -0
- package/dist/cognition/route-cognition-delta.js +350 -0
- package/dist/cognition/summary-delta.js +279 -0
- package/dist/cognition/validate.js +162 -0
- package/dist/config/defaults.js +98 -0
- package/dist/config/doctor.js +273 -0
- package/dist/config/index.js +14 -0
- package/dist/config/loader.js +58 -0
- package/dist/config/provider-detect.js +60 -0
- package/dist/config/provider-detect.test.js +119 -0
- package/dist/config/schema.js +2 -0
- package/dist/config/show.js +44 -0
- package/dist/config/show.test.js +42 -0
- package/dist/config/validator.js +675 -0
- package/dist/config/validator.test.js +77 -0
- package/dist/docs/canon-check.js +334 -0
- package/dist/docs/canon-check.test.js +207 -0
- package/dist/docs/doctrine.js +133 -0
- package/dist/docs/doctrine.test.js +139 -0
- package/dist/docs/index.js +168 -0
- package/dist/docs/index.test.js +83 -0
- package/dist/docs/ingest.js +295 -0
- package/dist/docs/ingest.test.js +71 -0
- package/dist/docs/migrate.js +165 -0
- package/dist/docs/migrate.test.js +136 -0
- package/dist/docs/seed-instructions.js +147 -0
- package/dist/docs/seed-instructions.test.js +137 -0
- package/dist/docs/validate-instructions.js +267 -0
- package/dist/docs/validate-instructions.test.js +238 -0
- package/dist/finalize/artifact-policy.js +214 -0
- package/dist/finalize/artifact-policy.test.js +84 -0
- package/dist/finalize/delivery-integrity.js +141 -0
- package/dist/finalize/finalize.test.js +210 -0
- package/dist/finalize/github.js +41 -0
- package/dist/finalize/index.js +474 -0
- package/dist/finalize/linear.js +213 -0
- package/dist/finalize/run-report.js +47 -0
- package/dist/finalize/steps/01-map-update.js +11 -0
- package/dist/finalize/steps/02-map-validate.js +11 -0
- package/dist/finalize/steps/03-schema-validate.js +11 -0
- package/dist/finalize/steps/04-run-checks.js +118 -0
- package/dist/finalize/steps/05-generate-report.js +14 -0
- package/dist/finalize/steps/06-commit.js +39 -0
- package/dist/finalize/steps/07-push.js +10 -0
- package/dist/finalize/steps/08-create-pr.js +9 -0
- package/dist/finalize/steps/09-update-state.js +9 -0
- package/dist/finalize/steps/10-append-jsonl.js +23 -0
- package/dist/finalize/steps/11-update-linear.js +22 -0
- package/dist/finalize/steps/12-archive.js +21 -0
- package/dist/graph/adapter/c/adapter.js +45 -0
- package/dist/graph/adapter/c/extract.js +135 -0
- package/dist/graph/adapter/c/index.js +10 -0
- package/dist/graph/adapter/c/runtime.js +67 -0
- package/dist/graph/adapter/cpp/adapter.js +45 -0
- package/dist/graph/adapter/cpp/extract.js +159 -0
- package/dist/graph/adapter/cpp/index.js +10 -0
- package/dist/graph/adapter/cpp/runtime.js +61 -0
- package/dist/graph/adapter/csharp/adapter.js +45 -0
- package/dist/graph/adapter/csharp/extract.js +127 -0
- package/dist/graph/adapter/csharp/index.js +10 -0
- package/dist/graph/adapter/csharp/runtime.js +61 -0
- package/dist/graph/adapter/dart/adapter.js +56 -0
- package/dist/graph/adapter/dart/extract.js +130 -0
- package/dist/graph/adapter/dart/index.js +10 -0
- package/dist/graph/adapter/dart/runtime.js +61 -0
- package/dist/graph/adapter/go/adapter.js +45 -0
- package/dist/graph/adapter/go/extract.js +112 -0
- package/dist/graph/adapter/go/index.js +10 -0
- package/dist/graph/adapter/go/runtime.js +58 -0
- package/dist/graph/adapter/kotlin-java/extract-common.js +83 -0
- package/dist/graph/adapter/kotlin-java/index.js +15 -0
- package/dist/graph/adapter/kotlin-java/java-adapter.js +45 -0
- package/dist/graph/adapter/kotlin-java/java-extract.js +42 -0
- package/dist/graph/adapter/kotlin-java/kotlin-adapter.js +48 -0
- package/dist/graph/adapter/kotlin-java/kotlin-extract.js +58 -0
- package/dist/graph/adapter/kotlin-java/runtime.js +75 -0
- package/dist/graph/adapter/python/adapter.js +45 -0
- package/dist/graph/adapter/python/extract.js +121 -0
- package/dist/graph/adapter/python/index.js +10 -0
- package/dist/graph/adapter/python/runtime.js +70 -0
- package/dist/graph/adapter/registry.js +82 -0
- package/dist/graph/adapter/rust/adapter.js +45 -0
- package/dist/graph/adapter/rust/extract.js +110 -0
- package/dist/graph/adapter/rust/index.js +10 -0
- package/dist/graph/adapter/rust/runtime.js +74 -0
- package/dist/graph/adapter/shell/adapter.js +34 -0
- package/dist/graph/adapter/shell/extract.js +98 -0
- package/dist/graph/adapter/shell/index.js +8 -0
- package/dist/graph/adapter/svelte/adapter.js +91 -0
- package/dist/graph/adapter/svelte/extract.js +41 -0
- package/dist/graph/adapter/svelte/index.js +8 -0
- package/dist/graph/adapter/swift/adapter.js +45 -0
- package/dist/graph/adapter/swift/extract.js +120 -0
- package/dist/graph/adapter/swift/index.js +10 -0
- package/dist/graph/adapter/swift/runtime.js +70 -0
- package/dist/graph/adapter/types.js +2 -0
- package/dist/graph/adapter/typescript-javascript/adapter.js +72 -0
- package/dist/graph/adapter/typescript-javascript/extract.js +115 -0
- package/dist/graph/adapter/typescript-javascript/index.js +10 -0
- package/dist/graph/adapter/typescript-javascript/runtime.js +97 -0
- package/dist/graph/capability/index.js +105 -0
- package/dist/graph/governance.js +104 -0
- package/dist/graph/parser/extract.js +5 -0
- package/dist/graph/parser/loader.js +2 -0
- package/dist/graph/parser/pipeline.js +194 -0
- package/dist/graph/query/index.js +277 -0
- package/dist/graph/query/types.js +5 -0
- package/dist/graph/resolver/build-edges.js +211 -0
- package/dist/graph/resolver/index.js +23 -0
- package/dist/graph/resolver/resolve-imports.js +168 -0
- package/dist/graph/store/adapter.js +132 -0
- package/dist/graph/store/queries.js +124 -0
- package/dist/graph/store/types.js +4 -0
- package/dist/ignore/defaults.js +41 -0
- package/dist/ignore/parser.js +39 -0
- package/dist/lint/map-reference-validator.js +72 -0
- package/dist/loop/abort.js +188 -0
- package/dist/loop/abort.test.js +151 -0
- package/dist/loop/adapters/__tests__/terminal-cli.test.js +92 -0
- package/dist/loop/adapters/agent-subtask.js +137 -0
- package/dist/loop/adapters/agent-subtask.test.js +151 -0
- package/dist/loop/adapters/cli-subtask-bridge.js +108 -0
- package/dist/loop/adapters/index.js +9 -0
- package/dist/loop/adapters/registry.js +17 -0
- package/dist/loop/adapters/terminal-cli.js +403 -0
- package/dist/loop/adapters/types.js +2 -0
- package/dist/loop/adapters/worker-instructions.js +85 -0
- package/dist/loop/body-parser.js +226 -0
- package/dist/loop/bootstrap-packet.js +97 -0
- package/dist/loop/bootstrap-packet.test.js +171 -0
- package/dist/loop/branch-governance.js +54 -0
- package/dist/loop/budget.js +63 -0
- package/dist/loop/budget.test.js +163 -0
- package/dist/loop/checkpoint.js +325 -0
- package/dist/loop/compact-return.js +59 -0
- package/dist/loop/continue.js +645 -0
- package/dist/loop/continue.test.js +337 -0
- package/dist/loop/dispatch-boundary.js +335 -0
- package/dist/loop/dispatch-state.js +223 -0
- package/dist/loop/dispatch-state.test.js +186 -0
- package/dist/loop/dispatch.js +1236 -0
- package/dist/loop/dispatch.test.js +499 -0
- package/dist/loop/ephemeral-smoke.test.js +160 -0
- package/dist/loop/evidence-backfill.js +216 -0
- package/dist/loop/execution-adapter.js +141 -0
- package/dist/loop/execution-adapter.test.js +66 -0
- package/dist/loop/finalize-evidence.js +169 -0
- package/dist/loop/git-custody.js +250 -0
- package/dist/loop/impl-packet-input.js +38 -0
- package/dist/loop/index.js +270 -0
- package/dist/loop/ledger.js +49 -0
- package/dist/loop/ledger.test.js +127 -0
- package/dist/loop/lifecycle-dispatch.js +348 -0
- package/dist/loop/lifecycle-dispatch.test.js +318 -0
- package/dist/loop/lifecycle.js +93 -0
- package/dist/loop/lifecycle.test.js +150 -0
- package/dist/loop/orphan-recovery.js +276 -0
- package/dist/loop/orphan-recovery.test.js +236 -0
- package/dist/loop/parent.js +1566 -0
- package/dist/loop/parent.test.js +738 -0
- package/dist/loop/provider-sealed-packet.test.js +164 -0
- package/dist/loop/resume.js +295 -0
- package/dist/loop/resume.test.js +172 -0
- package/dist/loop/run-bootstrap.js +234 -0
- package/dist/loop/run-preflight.js +203 -0
- package/dist/loop/status.js +665 -0
- package/dist/loop/status.test.js +235 -0
- package/dist/loop/wait.js +86 -0
- package/dist/loop/worker-packet.js +406 -0
- package/dist/loop/worker-packet.test.js +243 -0
- package/dist/loop/worker-prompt.js +260 -0
- package/dist/loop/worker-prompt.test.js +247 -0
- package/dist/loop/worker.js +498 -0
- package/dist/loop/worker.test.js +372 -0
- package/dist/map/atlas.js +77 -0
- package/dist/map/backfill.js +171 -0
- package/dist/map/backfill.test.js +120 -0
- package/dist/map/index.js +255 -0
- package/dist/map/inference.js +73 -0
- package/dist/map/inference.test.js +35 -0
- package/dist/map/query.js +157 -0
- package/dist/map/query.test.js +174 -0
- package/dist/map/update.js +355 -0
- package/dist/map/update.test.js +67 -0
- package/dist/map/validate.js +153 -0
- package/dist/map/validate.test.js +90 -0
- package/dist/map/welfare.js +86 -0
- package/dist/mcp/lib/invoke.js +35 -0
- package/dist/mcp/lib/redact.js +24 -0
- package/dist/mcp/lib/redact.test.js +112 -0
- package/dist/mcp/lib/root.js +41 -0
- package/dist/mcp/lib/root.test.js +114 -0
- package/dist/mcp/server.js +15 -0
- package/dist/mcp/tools/claim-child.js +119 -0
- package/dist/mcp/tools/claim-child.test.js +69 -0
- package/dist/mcp/tools/current-state.js +73 -0
- package/dist/mcp/tools/current-state.test.js +114 -0
- package/dist/mcp/tools/dispatch-result.js +186 -0
- package/dist/mcp/tools/dispatch-result.test.js +109 -0
- package/dist/mcp/tools/index.js +222 -0
- package/dist/mcp/tools/index.test.js +172 -0
- package/dist/mcp/tools/linear.js +74 -0
- package/dist/mcp/tools/loop-continue.js +159 -0
- package/dist/mcp/tools/loop-continue.test.js +186 -0
- package/dist/mcp/tools/loop-dry-run.js +15 -0
- package/dist/mcp/tools/status.js +65 -0
- package/dist/medic/chart-id.js +75 -0
- package/dist/medic/chart-schema.js +139 -0
- package/dist/runs/index.js +119 -0
- package/dist/runs/index.test.js +184 -0
- package/dist/runs/ledger-tail.js +15 -0
- package/dist/runs/list.js +29 -0
- package/dist/runs/reconcile.js +74 -0
- package/dist/runs/show.js +10 -0
- package/dist/runtime/audit/logger.js +15 -0
- package/dist/runtime/audit/reader.js +42 -0
- package/dist/runtime/checkpoint.js +89 -0
- package/dist/runtime/continuation/confirmed.js +225 -0
- package/dist/runtime/continuation/confirmed.test.js +421 -0
- package/dist/runtime/continuation/dry-run.js +74 -0
- package/dist/runtime/execution-window.js +106 -0
- package/dist/runtime/scheduling/child-selector.js +16 -0
- package/dist/runtime/state.js +43 -0
- package/dist/runtime/status.js +46 -0
- package/dist/runtime/verification/envelope.js +80 -0
- package/dist/runtime/verification/fingerprint.js +26 -0
- package/dist/skill-packet/cli.test.js +105 -0
- package/dist/skill-packet/generator.js +225 -0
- package/dist/skill-packet/generator.test.js +159 -0
- package/dist/skill-packet/index.js +49 -0
- package/dist/skill-packet/types.js +2 -0
- package/dist/smartdocs-engine/audit.js +194 -0
- package/dist/smartdocs-engine/audit.test.js +122 -0
- package/dist/smartdocs-engine/canon-check.js +340 -0
- package/dist/smartdocs-engine/canon-check.test.js +234 -0
- package/dist/smartdocs-engine/doctrine.js +633 -0
- package/dist/smartdocs-engine/doctrine.test.js +374 -0
- package/dist/smartdocs-engine/index.js +456 -0
- package/dist/smartdocs-engine/index.test.js +82 -0
- package/dist/smartdocs-engine/ingest.js +495 -0
- package/dist/smartdocs-engine/ingest.test.js +163 -0
- package/dist/smartdocs-engine/migrate.js +217 -0
- package/dist/smartdocs-engine/migrate.test.js +200 -0
- package/dist/smartdocs-engine/seed-instructions.js +335 -0
- package/dist/smartdocs-engine/seed-instructions.test.js +275 -0
- package/dist/smartdocs-engine/smartdoc-ignore.js +258 -0
- package/dist/smartdocs-engine/smartdoc-ignore.test.js +146 -0
- package/dist/smartdocs-engine/validate-instructions.js +366 -0
- package/dist/smartdocs-engine/validate-instructions.test.js +283 -0
- package/dist/tracker/adapters/github/index.js +305 -0
- package/dist/tracker/adapters/jira/index.js +293 -0
- package/dist/tracker/adapters/linear/http-errors.test.js +51 -0
- package/dist/tracker/adapters/linear/index.js +621 -0
- package/dist/tracker/adapters/linear/index.test.js +238 -0
- package/dist/tracker/adapters/linear.js +50 -0
- package/dist/tracker/adapters/local/index.js +10 -0
- package/dist/tracker/adapters/local-file/index.js +159 -0
- package/dist/tracker/adapters/mcp-bridge.js +158 -0
- package/dist/tracker/adapters/mcp-bridge.test.js +12 -0
- package/dist/tracker/adapters/spec/index.js +146 -0
- package/dist/tracker/capabilities.js +2 -0
- package/dist/tracker/index.js +49 -0
- package/dist/tracker/lifecycle-policy.js +139 -0
- package/dist/tracker/lifecycle-transition.js +164 -0
- package/dist/tracker/local-graph.js +98 -0
- package/dist/tracker/migration.js +56 -0
- package/dist/tracker/schema.js +86 -0
- package/dist/tracker/sync/index.js +404 -0
- package/dist/tracker/sync/queue-store.js +95 -0
- package/dist/tracker/sync/queue-store.test.js +104 -0
- package/dist/tracker/types.js +11 -0
- package/dist/types/linear.js +3 -0
- package/dist/types/result-packet.js +12 -0
- package/dist/types/runtime-state.js +2 -0
- package/dist/types/work-contract.js +2 -0
- package/dist/utils/monotonic-timestamp.js +13 -0
- package/dist/utils/monotonic-timestamp.test.js +19 -0
- package/package.json +64 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// HARD CONSTRAINTS — do not remove these comments:
|
|
3
|
+
// DO NOT implement unrestricted continuation (no open-ended "run everything" path)
|
|
4
|
+
// DO NOT implement arbitrary shell passthrough (no exec/spawn of user-provided commands)
|
|
5
|
+
// DO NOT implement direct git operations (no git commit/push from this handler)
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.handleLoopContinueConfirmed = handleLoopContinueConfirmed;
|
|
8
|
+
const envelope_js_1 = require("../../runtime/verification/envelope.js");
|
|
9
|
+
const logger_js_1 = require("../../runtime/audit/logger.js");
|
|
10
|
+
const state_js_1 = require("../../runtime/state.js");
|
|
11
|
+
const confirmed_js_1 = require("../../runtime/continuation/confirmed.js");
|
|
12
|
+
// Per-artifact-dir lock: prevents two concurrent confirmations from both passing
|
|
13
|
+
// validation before either writes active_child to disk. Set operations are
|
|
14
|
+
// synchronous so the check-and-add is atomic within a single Node.js event loop tick.
|
|
15
|
+
const pendingConfirmations = new Set();
|
|
16
|
+
function parseEnvelope(args) {
|
|
17
|
+
const artifact_dir = typeof args["artifact_dir"] === "string" ? args["artifact_dir"] : "polaris-run";
|
|
18
|
+
const required = [
|
|
19
|
+
"run_id",
|
|
20
|
+
"expected_step_cursor",
|
|
21
|
+
"fingerprint",
|
|
22
|
+
"nonce",
|
|
23
|
+
"issued_at",
|
|
24
|
+
"expires_at",
|
|
25
|
+
"requested_action",
|
|
26
|
+
];
|
|
27
|
+
for (const key of required) {
|
|
28
|
+
if (typeof args[key] !== "string") {
|
|
29
|
+
throw new Error(`missing_or_invalid_field: ${key} must be a string`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (typeof args["runtime_generation"] !== "number") {
|
|
33
|
+
throw new Error("missing_or_invalid_field: runtime_generation must be a number");
|
|
34
|
+
}
|
|
35
|
+
if (args["requested_action"] !== "loop_continue") {
|
|
36
|
+
throw new Error(`invalid_field: requested_action must be "loop_continue", got "${String(args["requested_action"])}"`);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
artifact_dir,
|
|
40
|
+
run_id: args["run_id"],
|
|
41
|
+
expected_step_cursor: args["expected_step_cursor"],
|
|
42
|
+
fingerprint: args["fingerprint"],
|
|
43
|
+
runtime_generation: args["runtime_generation"],
|
|
44
|
+
issued_at: args["issued_at"],
|
|
45
|
+
expires_at: args["expires_at"],
|
|
46
|
+
nonce: args["nonce"],
|
|
47
|
+
requested_action: "loop_continue",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Handle a polaris_loop_continue_confirmed MCP tool call.
|
|
52
|
+
*
|
|
53
|
+
* Validates the supplied ContinuationApprovalEnvelope against the current live
|
|
54
|
+
* state (fresh disk read — never cached). On success, dispatches the next child
|
|
55
|
+
* via the confirmed continuation service and returns the dispatch result.
|
|
56
|
+
*
|
|
57
|
+
* Returns `{ ok: true, child_id, compact_return }` on success, or
|
|
58
|
+
* `{ ok: false, rejection: { check, reason, ... } }` on any failure.
|
|
59
|
+
*/
|
|
60
|
+
async function handleLoopContinueConfirmed(args) {
|
|
61
|
+
// Parse and validate input shape
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = parseEnvelope(args);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
error: "invalid_input",
|
|
70
|
+
message: err instanceof Error ? err.message : String(err),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const { artifact_dir, ...envelope } = parsed;
|
|
74
|
+
// Load state first — needed for audit events even if we reject early.
|
|
75
|
+
// Always a fresh read, never cached.
|
|
76
|
+
const state = await (0, state_js_1.loadState)(artifact_dir);
|
|
77
|
+
if (state === null) {
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
error: "state_not_found",
|
|
81
|
+
message: `No current-state.json found for artifact_dir="${artifact_dir}"`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// In-memory lock: if another confirmation is in flight for this artifact_dir,
|
|
85
|
+
// reject immediately. Load state first so we can write a proper audit event.
|
|
86
|
+
// Set operations are synchronous, so check+add is atomic within one event-loop tick.
|
|
87
|
+
if (pendingConfirmations.has(artifact_dir)) {
|
|
88
|
+
await (0, logger_js_1.appendAuditEvent)(artifact_dir, {
|
|
89
|
+
event_type: "mutation_rejected",
|
|
90
|
+
run_id: state.run_id,
|
|
91
|
+
step_cursor: state.step_cursor,
|
|
92
|
+
operator: "mcp",
|
|
93
|
+
operation: "loop_continue_confirmed",
|
|
94
|
+
approval_fingerprint: envelope.fingerprint,
|
|
95
|
+
result: "rejected",
|
|
96
|
+
rejection_reason: "active_child: concurrent_execution",
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
rejection: { check: "active_child", reason: "concurrent_execution" },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
pendingConfirmations.add(artifact_dir);
|
|
104
|
+
try {
|
|
105
|
+
// Validate envelope against live state (all 9 structural checks)
|
|
106
|
+
const validation = (0, envelope_js_1.validateEnvelope)(state, envelope);
|
|
107
|
+
if (!validation.ok) {
|
|
108
|
+
await (0, logger_js_1.appendAuditEvent)(artifact_dir, {
|
|
109
|
+
event_type: "mutation_rejected",
|
|
110
|
+
run_id: state.run_id,
|
|
111
|
+
step_cursor: state.step_cursor,
|
|
112
|
+
operator: "mcp",
|
|
113
|
+
operation: "loop_continue_confirmed",
|
|
114
|
+
approval_fingerprint: envelope.fingerprint,
|
|
115
|
+
result: "rejected",
|
|
116
|
+
rejection_reason: `${validation.failure.check}: ${validation.failure.reason}`,
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
rejection: {
|
|
121
|
+
check: validation.failure.check,
|
|
122
|
+
reason: validation.failure.reason,
|
|
123
|
+
...(validation.failure.expected !== undefined && { expected: validation.failure.expected }),
|
|
124
|
+
...(validation.failure.actual !== undefined && { actual: validation.failure.actual }),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Validation passed — record mutation_requested
|
|
129
|
+
await (0, logger_js_1.appendAuditEvent)(artifact_dir, {
|
|
130
|
+
event_type: "mutation_requested",
|
|
131
|
+
run_id: state.run_id,
|
|
132
|
+
step_cursor: state.step_cursor,
|
|
133
|
+
operator: "mcp",
|
|
134
|
+
operation: "loop_continue_confirmed",
|
|
135
|
+
approval_fingerprint: envelope.fingerprint,
|
|
136
|
+
result: "ok",
|
|
137
|
+
});
|
|
138
|
+
// Delegate checkpoint, active_child lease write, and mutation_approved audit to
|
|
139
|
+
// the confirmed continuation service.
|
|
140
|
+
const adapterOverride = typeof args["adapterOverride"] === "string"
|
|
141
|
+
? args["adapterOverride"]
|
|
142
|
+
: undefined;
|
|
143
|
+
const _adapterFactory = typeof args["_adapterFactory"] === "function"
|
|
144
|
+
? args["_adapterFactory"]
|
|
145
|
+
: undefined;
|
|
146
|
+
const dispatchResult = await (0, confirmed_js_1.dispatchConfirmedContinuation)({ artifact_dir, envelope, adapterOverride, _adapterFactory });
|
|
147
|
+
if (!dispatchResult.ok) {
|
|
148
|
+
return { ok: false, rejection: dispatchResult.rejection };
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
ok: true,
|
|
152
|
+
child_id: dispatchResult.child_id,
|
|
153
|
+
compact_return: dispatchResult.compact_return,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
pendingConfirmations.delete(artifact_dir);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* POL-96: End-to-end tests for handleLoopContinueConfirmed MCP response shape.
|
|
4
|
+
*
|
|
5
|
+
* Covers:
|
|
6
|
+
* 1. Full success path — response includes ok, child_id, compact_return
|
|
7
|
+
* 2. Dispatch failure path (adapter_mode / terminal-cli) — response includes ok: false, rejection
|
|
8
|
+
* 3. pendingConfirmations lock released on error (state_not_found)
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
44
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
45
|
+
};
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
const vitest_1 = require("vitest");
|
|
48
|
+
const promises_1 = require("node:fs/promises");
|
|
49
|
+
const node_crypto_1 = require("node:crypto");
|
|
50
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
51
|
+
const dry_run_js_1 = require("../../runtime/continuation/dry-run.js");
|
|
52
|
+
const loop_continue_js_1 = require("./loop-continue.js");
|
|
53
|
+
const confirmedModule = __importStar(require("../../runtime/continuation/confirmed.js"));
|
|
54
|
+
// Mock the confirmed module so we can control dispatchConfirmedContinuation in Test 1
|
|
55
|
+
vitest_1.vi.mock("../../runtime/continuation/confirmed.js", async (importOriginal) => {
|
|
56
|
+
const original = await importOriginal();
|
|
57
|
+
return {
|
|
58
|
+
...original,
|
|
59
|
+
dispatchConfirmedContinuation: vitest_1.vi.fn(original.dispatchConfirmedContinuation),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
const ARTIFACTS_ROOT = node_path_1.default.join(process.cwd(), ".taskchain_artifacts");
|
|
63
|
+
function makeRunningState(overrides = {}) {
|
|
64
|
+
return {
|
|
65
|
+
schema_version: "1.0",
|
|
66
|
+
run_id: "polaris-run-test-pol96",
|
|
67
|
+
cluster_id: "POL-88",
|
|
68
|
+
active_child: null,
|
|
69
|
+
completed_children: ["POL-91", "POL-92", "POL-93"],
|
|
70
|
+
open_children: ["POL-96"],
|
|
71
|
+
step_cursor: "06-decide-continuation",
|
|
72
|
+
context_budget: { children_completed: 3, max_children_per_session: 4 },
|
|
73
|
+
status: "running",
|
|
74
|
+
runtime_generation: 1,
|
|
75
|
+
orchestration_mode: "bootstrap",
|
|
76
|
+
continuation_epoch: 0,
|
|
77
|
+
...overrides,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function writeStateFile(artifactDir, state) {
|
|
81
|
+
const dir = node_path_1.default.join(ARTIFACTS_ROOT, artifactDir);
|
|
82
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
83
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(dir, "current-state.json"), JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
84
|
+
}
|
|
85
|
+
function buildEnvelope(template, overrides = {}) {
|
|
86
|
+
const now = new Date();
|
|
87
|
+
return {
|
|
88
|
+
...template,
|
|
89
|
+
issued_at: now.toISOString(),
|
|
90
|
+
expires_at: new Date(now.getTime() + 5 * 60 * 1000).toISOString(),
|
|
91
|
+
...overrides,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
let testArtifactDir;
|
|
95
|
+
(0, vitest_1.beforeEach)(() => {
|
|
96
|
+
testArtifactDir = `test-pol96-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
|
|
97
|
+
vitest_1.vi.mocked(confirmedModule.dispatchConfirmedContinuation).mockRestore();
|
|
98
|
+
});
|
|
99
|
+
(0, vitest_1.afterEach)(async () => {
|
|
100
|
+
const dir = node_path_1.default.join(ARTIFACTS_ROOT, testArtifactDir);
|
|
101
|
+
await (0, promises_1.rm)(dir, { recursive: true, force: true });
|
|
102
|
+
});
|
|
103
|
+
(0, vitest_1.describe)("handleLoopContinueConfirmed — MCP response shape", () => {
|
|
104
|
+
(0, vitest_1.it)("Test 1: full success path returns ok, child_id, and compact_return", async () => {
|
|
105
|
+
await writeStateFile(testArtifactDir, makeRunningState());
|
|
106
|
+
const dryRunResult = await (0, dry_run_js_1.executeDryRun)({
|
|
107
|
+
artifact_dir: testArtifactDir,
|
|
108
|
+
expected_step_cursor: "06-decide-continuation",
|
|
109
|
+
});
|
|
110
|
+
(0, vitest_1.expect)(dryRunResult.ok).toBe(true);
|
|
111
|
+
if (!dryRunResult.ok)
|
|
112
|
+
return;
|
|
113
|
+
const envelope = buildEnvelope(dryRunResult.preview.approval_template);
|
|
114
|
+
// Override dispatch to return a controlled success result (avoids AgentSubtaskAdapter I/O)
|
|
115
|
+
vitest_1.vi.mocked(confirmedModule.dispatchConfirmedContinuation).mockResolvedValueOnce({
|
|
116
|
+
ok: true,
|
|
117
|
+
child_id: "POL-96",
|
|
118
|
+
compact_return: { status: "done", state_updated: true, exit_code: 0 },
|
|
119
|
+
});
|
|
120
|
+
const result = await (0, loop_continue_js_1.handleLoopContinueConfirmed)({
|
|
121
|
+
artifact_dir: testArtifactDir,
|
|
122
|
+
...envelope,
|
|
123
|
+
});
|
|
124
|
+
(0, vitest_1.expect)(result["ok"]).toBe(true);
|
|
125
|
+
(0, vitest_1.expect)(result["child_id"]).toBe("POL-96");
|
|
126
|
+
(0, vitest_1.expect)(result["compact_return"]).toBeDefined();
|
|
127
|
+
const compactReturn = result["compact_return"];
|
|
128
|
+
(0, vitest_1.expect)(compactReturn["status"]).toBe("done");
|
|
129
|
+
(0, vitest_1.expect)(compactReturn["state_updated"]).toBe(true);
|
|
130
|
+
(0, vitest_1.expect)(vitest_1.vi.mocked(confirmedModule.dispatchConfirmedContinuation)).toHaveBeenCalledTimes(1);
|
|
131
|
+
});
|
|
132
|
+
(0, vitest_1.it)("Test 2: dispatch failure (terminal-cli adapter) returns ok: false with rejection", async () => {
|
|
133
|
+
await writeStateFile(testArtifactDir, makeRunningState());
|
|
134
|
+
const dryRunResult = await (0, dry_run_js_1.executeDryRun)({
|
|
135
|
+
artifact_dir: testArtifactDir,
|
|
136
|
+
expected_step_cursor: "06-decide-continuation",
|
|
137
|
+
});
|
|
138
|
+
(0, vitest_1.expect)(dryRunResult.ok).toBe(true);
|
|
139
|
+
if (!dryRunResult.ok)
|
|
140
|
+
return;
|
|
141
|
+
const envelope = buildEnvelope(dryRunResult.preview.approval_template);
|
|
142
|
+
// Use adapterOverride: "terminal-cli" via args to trigger manual_dispatch_required
|
|
143
|
+
// (real code path — no mock needed)
|
|
144
|
+
const result = await (0, loop_continue_js_1.handleLoopContinueConfirmed)({
|
|
145
|
+
artifact_dir: testArtifactDir,
|
|
146
|
+
adapterOverride: "terminal-cli",
|
|
147
|
+
...envelope,
|
|
148
|
+
});
|
|
149
|
+
(0, vitest_1.expect)(result["ok"]).toBe(false);
|
|
150
|
+
(0, vitest_1.expect)(result["rejection"]).toBeDefined();
|
|
151
|
+
const rejection = result["rejection"];
|
|
152
|
+
(0, vitest_1.expect)(rejection["check"]).toBe("adapter_mode");
|
|
153
|
+
(0, vitest_1.expect)(rejection["reason"]).toBe("manual_dispatch_required");
|
|
154
|
+
});
|
|
155
|
+
(0, vitest_1.it)("Test 3: pendingConfirmations lock released on state_not_found error", async () => {
|
|
156
|
+
// No state written — triggers state_not_found path
|
|
157
|
+
const result = await (0, loop_continue_js_1.handleLoopContinueConfirmed)({
|
|
158
|
+
artifact_dir: testArtifactDir,
|
|
159
|
+
run_id: "polaris-run-test-pol96",
|
|
160
|
+
expected_step_cursor: "06-decide-continuation",
|
|
161
|
+
fingerprint: "dummy-fingerprint",
|
|
162
|
+
nonce: (0, node_crypto_1.randomUUID)(),
|
|
163
|
+
issued_at: new Date().toISOString(),
|
|
164
|
+
expires_at: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
165
|
+
requested_action: "loop_continue",
|
|
166
|
+
runtime_generation: 1,
|
|
167
|
+
});
|
|
168
|
+
(0, vitest_1.expect)(result["ok"]).toBe(false);
|
|
169
|
+
(0, vitest_1.expect)(result["error"]).toBe("state_not_found");
|
|
170
|
+
// Verify lock is released: a subsequent call for the same artifact_dir should
|
|
171
|
+
// return state_not_found again, not concurrent_execution
|
|
172
|
+
const result2 = await (0, loop_continue_js_1.handleLoopContinueConfirmed)({
|
|
173
|
+
artifact_dir: testArtifactDir,
|
|
174
|
+
run_id: "polaris-run-test-pol96",
|
|
175
|
+
expected_step_cursor: "06-decide-continuation",
|
|
176
|
+
fingerprint: "dummy-fingerprint",
|
|
177
|
+
nonce: (0, node_crypto_1.randomUUID)(),
|
|
178
|
+
issued_at: new Date().toISOString(),
|
|
179
|
+
expires_at: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
180
|
+
requested_action: "loop_continue",
|
|
181
|
+
runtime_generation: 1,
|
|
182
|
+
});
|
|
183
|
+
(0, vitest_1.expect)(result2["ok"]).toBe(false);
|
|
184
|
+
(0, vitest_1.expect)(result2["error"]).toBe("state_not_found");
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DryRunInputSchema = void 0;
|
|
4
|
+
exports.handleLoopContinueDryRun = handleLoopContinueDryRun;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const dry_run_js_1 = require("../../runtime/continuation/dry-run.js");
|
|
7
|
+
exports.DryRunInputSchema = zod_1.z.object({
|
|
8
|
+
artifact_dir: zod_1.z.string().default("polaris-run"),
|
|
9
|
+
expected_step_cursor: zod_1.z.string(),
|
|
10
|
+
});
|
|
11
|
+
async function handleLoopContinueDryRun(args) {
|
|
12
|
+
const input = exports.DryRunInputSchema.parse(args);
|
|
13
|
+
const result = await (0, dry_run_js_1.executeDryRun)(input);
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handlePolarisStatus = handlePolarisStatus;
|
|
4
|
+
exports.handlePolarisLoopStatus = handlePolarisLoopStatus;
|
|
5
|
+
const invoke_js_1 = require("../lib/invoke.js");
|
|
6
|
+
const root_js_1 = require("../lib/root.js");
|
|
7
|
+
function normalizeBudget(budget) {
|
|
8
|
+
if (!budget)
|
|
9
|
+
return undefined;
|
|
10
|
+
const completed = typeof budget.children_completed === "number" ? budget.children_completed : 0;
|
|
11
|
+
const max = typeof budget.max_children_per_session === "number" ? budget.max_children_per_session : 3;
|
|
12
|
+
return {
|
|
13
|
+
children_completed: completed,
|
|
14
|
+
max_children_per_session: max,
|
|
15
|
+
remaining: Math.max(0, max - completed),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function buildStatusResponse(raw) {
|
|
19
|
+
const p = raw;
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
run_id: p.run_id ?? null,
|
|
23
|
+
cluster_id: p.cluster_id ?? null,
|
|
24
|
+
status: p.status ?? null,
|
|
25
|
+
active_child: p.active_child ?? null,
|
|
26
|
+
completed_children: p.completed_children ?? [],
|
|
27
|
+
open_children: p.open_children ?? [],
|
|
28
|
+
step_cursor: p.step_cursor ?? null,
|
|
29
|
+
context_budget: normalizeBudget(p.context_budget),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function errorResponse(error, message, hint) {
|
|
33
|
+
const r = { ok: false, error, message };
|
|
34
|
+
if (hint)
|
|
35
|
+
r["hint"] = hint;
|
|
36
|
+
return r;
|
|
37
|
+
}
|
|
38
|
+
async function handlePolarisStatus() {
|
|
39
|
+
const repoRoot = (0, root_js_1.resolveRepoRoot)();
|
|
40
|
+
try {
|
|
41
|
+
const raw = (0, invoke_js_1.invokePolarisJson)(repoRoot, ["status", "--json"]);
|
|
42
|
+
return buildStatusResponse(raw);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (err instanceof invoke_js_1.InvokeError) {
|
|
46
|
+
const isNotFound = err.stderr.includes("cannot read state file") || err.message.includes("cannot read");
|
|
47
|
+
return errorResponse(isNotFound ? "state_not_found" : "invoke_failed", err.message, isNotFound ? "Run a polaris cluster session first to create current-state.json" : undefined);
|
|
48
|
+
}
|
|
49
|
+
return errorResponse("unknown", String(err));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function handlePolarisLoopStatus() {
|
|
53
|
+
const repoRoot = (0, root_js_1.resolveRepoRoot)();
|
|
54
|
+
try {
|
|
55
|
+
const raw = (0, invoke_js_1.invokePolarisJson)(repoRoot, ["loop", "status", "--json"]);
|
|
56
|
+
return buildStatusResponse(raw);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (err instanceof invoke_js_1.InvokeError) {
|
|
60
|
+
const isNotFound = err.stderr.includes("cannot read state file") || err.message.includes("cannot read");
|
|
61
|
+
return errorResponse(isNotFound ? "state_not_found" : "invoke_failed", err.message, isNotFound ? "Run a polaris cluster session first to create current-state.json" : undefined);
|
|
62
|
+
}
|
|
63
|
+
return errorResponse("unknown", String(err));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseChartId = parseChartId;
|
|
4
|
+
exports.formatChartId = formatChartId;
|
|
5
|
+
exports.getTodayDate = getTodayDate;
|
|
6
|
+
exports.getMaxSequenceForDate = getMaxSequenceForDate;
|
|
7
|
+
exports.generateNextChartId = generateNextChartId;
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
/**
|
|
10
|
+
* Parse a Chart ID string into its components
|
|
11
|
+
*/
|
|
12
|
+
function parseChartId(chartId) {
|
|
13
|
+
const match = chartId.match(/^CHART-(\d{4}-\d{2}-\d{2})-(\d{3})$/);
|
|
14
|
+
if (!match)
|
|
15
|
+
return null;
|
|
16
|
+
return {
|
|
17
|
+
full: chartId,
|
|
18
|
+
date: match[1],
|
|
19
|
+
sequence: parseInt(match[2], 10),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Format a Chart ID from its components
|
|
24
|
+
*/
|
|
25
|
+
function formatChartId(date, sequence) {
|
|
26
|
+
return `CHART-${date}-${sequence.toString().padStart(3, "0")}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get today's date in YYYY-MM-DD format (UTC)
|
|
30
|
+
*/
|
|
31
|
+
function getTodayDate() {
|
|
32
|
+
const now = new Date();
|
|
33
|
+
const year = now.getUTCFullYear();
|
|
34
|
+
const month = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
35
|
+
const day = String(now.getUTCDate()).padStart(2, "0");
|
|
36
|
+
return `${year}-${month}-${day}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Scan the charts directory for existing chart IDs on a given date
|
|
40
|
+
* and return the maximum sequence number found
|
|
41
|
+
*/
|
|
42
|
+
function getMaxSequenceForDate(chartsDir, date) {
|
|
43
|
+
if (!(0, node_fs_1.existsSync)(chartsDir))
|
|
44
|
+
return 0;
|
|
45
|
+
const files = (0, node_fs_1.readdirSync)(chartsDir);
|
|
46
|
+
const prefix = `CHART-${date}-`;
|
|
47
|
+
let maxSeq = 0;
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
// Strip file extension for parsing
|
|
50
|
+
const baseName = file.replace(/\.[^/.]+$/, "");
|
|
51
|
+
if (baseName.startsWith(prefix)) {
|
|
52
|
+
const parsed = parseChartId(baseName);
|
|
53
|
+
if (parsed && parsed.sequence > maxSeq) {
|
|
54
|
+
maxSeq = parsed.sequence;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return maxSeq;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generate the next Chart ID for today
|
|
62
|
+
*/
|
|
63
|
+
function generateNextChartId(chartsDir) {
|
|
64
|
+
const today = getTodayDate();
|
|
65
|
+
const maxSeq = getMaxSequenceForDate(chartsDir, today);
|
|
66
|
+
const nextSeq = maxSeq + 1;
|
|
67
|
+
if (nextSeq > 999) {
|
|
68
|
+
throw new RangeError(`Chart sequence overflow for ${today}: maximum sequence is 999, cannot generate sequence ${nextSeq}`);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
full: formatChartId(today, nextSeq),
|
|
72
|
+
date: today,
|
|
73
|
+
sequence: nextSeq,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REQUIRED_SECTIONS = exports.ChartFrontMatter = exports.ChartRelationship = exports.ChartRelationshipType = void 0;
|
|
4
|
+
exports.validateChartFrontMatter = validateChartFrontMatter;
|
|
5
|
+
exports.validateChartSections = validateChartSections;
|
|
6
|
+
exports.validateChart = validateChart;
|
|
7
|
+
const zod_1 = require("zod");
|
|
8
|
+
/**
|
|
9
|
+
* Supported chart relationship types
|
|
10
|
+
*/
|
|
11
|
+
exports.ChartRelationshipType = zod_1.z.enum([
|
|
12
|
+
"same_failure",
|
|
13
|
+
"edge_case_of",
|
|
14
|
+
"regression_of",
|
|
15
|
+
"caused_by",
|
|
16
|
+
"fixed_by",
|
|
17
|
+
"supersedes",
|
|
18
|
+
"duplicate_of",
|
|
19
|
+
]);
|
|
20
|
+
/**
|
|
21
|
+
* Chart relationship reference
|
|
22
|
+
*/
|
|
23
|
+
exports.ChartRelationship = zod_1.z.object({
|
|
24
|
+
chart_id: zod_1.z.string().regex(/^CHART-\d{4}-\d{2}-\d{2}-\d{3}$/, {
|
|
25
|
+
message: "chart_id must match format CHART-YYYY-MM-DD-NNN",
|
|
26
|
+
}),
|
|
27
|
+
relationship: exports.ChartRelationshipType,
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Chart front-matter schema
|
|
31
|
+
*/
|
|
32
|
+
exports.ChartFrontMatter = zod_1.z.object({
|
|
33
|
+
chart_id: zod_1.z.string().regex(/^CHART-\d{4}-\d{2}-\d{2}-\d{3}$/, {
|
|
34
|
+
message: "chart_id must match format CHART-YYYY-MM-DD-NNN",
|
|
35
|
+
}),
|
|
36
|
+
cluster_id: zod_1.z.string().min(1),
|
|
37
|
+
route: zod_1.z.string().min(1),
|
|
38
|
+
status: zod_1.z.string().min(1),
|
|
39
|
+
related_charts: zod_1.z.array(exports.ChartRelationship).optional(),
|
|
40
|
+
created: zod_1.z.string().datetime(),
|
|
41
|
+
updated: zod_1.z.string().datetime(),
|
|
42
|
+
drift_observations: zod_1.z.array(zod_1.z.string()).optional(),
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Required section headings in a chart
|
|
46
|
+
*/
|
|
47
|
+
exports.REQUIRED_SECTIONS = [
|
|
48
|
+
"Problem",
|
|
49
|
+
"Symptoms",
|
|
50
|
+
"Root Cause",
|
|
51
|
+
"Affected Files",
|
|
52
|
+
"Treatment",
|
|
53
|
+
"Validation",
|
|
54
|
+
"Prevention",
|
|
55
|
+
"When To Read This Chart",
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Validate chart front-matter
|
|
59
|
+
*/
|
|
60
|
+
function validateChartFrontMatter(frontMatter) {
|
|
61
|
+
const result = exports.ChartFrontMatter.safeParse(frontMatter);
|
|
62
|
+
if (!result.success) {
|
|
63
|
+
return {
|
|
64
|
+
valid: false,
|
|
65
|
+
errors: result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { valid: true, errors: [] };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if a markdown content contains all required section headings
|
|
72
|
+
*/
|
|
73
|
+
function validateChartSections(content) {
|
|
74
|
+
const errors = [];
|
|
75
|
+
for (const section of exports.REQUIRED_SECTIONS) {
|
|
76
|
+
// Check for section heading (## at level 2, or # at level 1)
|
|
77
|
+
const pattern = new RegExp(`^#{1,2}\\s*${section}\\s*$`, "m");
|
|
78
|
+
if (!pattern.test(content)) {
|
|
79
|
+
errors.push(`Missing required section: ${section}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
valid: errors.length === 0,
|
|
84
|
+
errors,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Validate a complete chart (front-matter and sections)
|
|
89
|
+
*/
|
|
90
|
+
function validateChart(content) {
|
|
91
|
+
const frontMatterErrors = [];
|
|
92
|
+
const sectionErrors = [];
|
|
93
|
+
// Extract front-matter
|
|
94
|
+
const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
95
|
+
if (!frontMatterMatch) {
|
|
96
|
+
return {
|
|
97
|
+
valid: false,
|
|
98
|
+
errors: ["Chart must have YAML front-matter delimited by ---"],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Parse YAML front-matter (simple key:value parsing)
|
|
102
|
+
const frontMatter = {};
|
|
103
|
+
const frontMatterLines = frontMatterMatch[1].split("\n");
|
|
104
|
+
for (const line of frontMatterLines) {
|
|
105
|
+
const colonIdx = line.indexOf(":");
|
|
106
|
+
if (colonIdx === -1)
|
|
107
|
+
continue;
|
|
108
|
+
const key = line.slice(0, colonIdx).trim();
|
|
109
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
110
|
+
// Remove quotes if present
|
|
111
|
+
const cleanedValue = value.replace(/^["']|["']$/g, "");
|
|
112
|
+
// Handle arrays (simple comma-separated for now)
|
|
113
|
+
if (cleanedValue.startsWith("[") && cleanedValue.endsWith("]")) {
|
|
114
|
+
const arrayContent = cleanedValue.slice(1, -1);
|
|
115
|
+
if (arrayContent.trim() === "") {
|
|
116
|
+
frontMatter[key] = [];
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
frontMatter[key] = arrayContent.split(",").map((s) => s.trim());
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (key === "related_charts") {
|
|
123
|
+
// Special handling for related_charts array of objects
|
|
124
|
+
// This is a simplified parser - in production you'd use a proper YAML parser
|
|
125
|
+
frontMatter[key] = [];
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
frontMatter[key] = cleanedValue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const frontMatterResult = validateChartFrontMatter(frontMatter);
|
|
132
|
+
frontMatterErrors.push(...frontMatterResult.errors);
|
|
133
|
+
const sectionResult = validateChartSections(content);
|
|
134
|
+
sectionErrors.push(...sectionResult.errors);
|
|
135
|
+
return {
|
|
136
|
+
valid: frontMatterErrors.length === 0 && sectionErrors.length === 0,
|
|
137
|
+
errors: [...frontMatterErrors, ...sectionErrors],
|
|
138
|
+
};
|
|
139
|
+
}
|