@mmerterden/multi-agent-pipeline 17.6.0 → 19.0.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/CHANGELOG.md +310 -0
- package/README.md +76 -18
- package/README.tr.md +55 -16
- package/docs/adr/0002-instruction-driven-flag.md +1 -0
- package/docs/adr/0005-lazy-phase-docs.md +11 -1
- package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +1 -0
- package/docs/adr/0010-own-code-graph.md +1 -0
- package/docs/adr/0011-dormant-ci.md +25 -1
- package/docs/adr/0014-six-phase-consolidation.md +134 -0
- package/docs/adr/README.md +2 -1
- package/docs/architecture.md +37 -38
- package/docs/best-practices.md +1 -1
- package/docs/ecosystem.md +37 -26
- package/docs/engineering.md +1 -1
- package/docs/facts.json +45 -0
- package/docs/features.md +54 -53
- package/docs/performance.md +5 -5
- package/docs/recovery-guide.md +9 -9
- package/docs/server-readiness.md +188 -0
- package/docs/token-budget-history.md +3 -1
- package/index.js +18 -3
- package/install/_codex-agents.mjs +1 -1
- package/install/_common.mjs +42 -17
- package/install/_dev-only-files.mjs +8 -0
- package/install/_unattended-profile.mjs +113 -0
- package/install/index.mjs +48 -0
- package/install/templates/claude-hooks.json +1 -1
- package/install/templates/codex-instructions.md +1 -1
- package/install/templates/copilot-instructions.md +28 -28
- package/manifest.json +1065 -0
- package/package.json +6 -3
- package/pipeline/agents/dev-critic.md +3 -3
- package/pipeline/commands/figma-to-swiftui.md +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +8 -8
- package/pipeline/commands/multi-agent/analysis/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +7 -7
- package/pipeline/commands/multi-agent/channels/SKILL.md +15 -15
- package/pipeline/commands/multi-agent/diff-explain/SKILL.md +6 -6
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/graph/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/help/SKILL.md +62 -62
- package/pipeline/commands/multi-agent/language/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/local/SKILL.md +11 -11
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +13 -13
- package/pipeline/commands/multi-agent/log/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/model/SKILL.md +69 -0
- package/pipeline/commands/multi-agent/refactor/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/resume/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/resume-local/SKILL.md +19 -17
- package/pipeline/commands/multi-agent/review/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/route-off/SKILL.md +36 -0
- package/pipeline/commands/multi-agent/route-on/SKILL.md +74 -0
- package/pipeline/commands/multi-agent/route-status/SKILL.md +56 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/status/SKILL.md +54 -23
- package/pipeline/commands/multi-agent/steer/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/sync/SKILL.md +12 -13
- package/pipeline/commands/multi-agent/test/SKILL.md +1 -1
- package/pipeline/lib/_jira-auth.sh +8 -0
- package/pipeline/lib/analysis-jira-write.sh +32 -0
- package/pipeline/lib/ask-choice.sh +13 -2
- package/pipeline/lib/autopilot-state.sh +8 -0
- package/pipeline/lib/credential-inventory.sh +1 -1
- package/pipeline/lib/fatal.mjs +129 -0
- package/pipeline/lib/fetch-fortify.sh +1 -1
- package/pipeline/lib/figma-mcp-refresh.sh +18 -0
- package/pipeline/lib/figma-screenshot.sh +18 -0
- package/pipeline/lib/invoked-directly.mjs +43 -0
- package/pipeline/lib/jira-publish.sh +42 -0
- package/pipeline/lib/md2confluence-v3.py +47 -0
- package/pipeline/lib/model-rung.sh +142 -0
- package/pipeline/lib/outbound-gate.mjs +175 -0
- package/pipeline/lib/phase-schema.mjs +88 -0
- package/pipeline/lib/plan-todos.sh +32 -11
- package/pipeline/lib/post-pr-review.sh +77 -8
- package/pipeline/lib/repo-hygiene.sh +8 -3
- package/pipeline/lib/require-jq.sh +40 -0
- package/pipeline/lib/route-state.sh +161 -0
- package/pipeline/lib/run-paths.sh +335 -0
- package/pipeline/multi-agent-refs/_account-picker.md +1 -1
- package/pipeline/multi-agent-refs/_dev-context.md +1 -1
- package/pipeline/multi-agent-refs/_input-parser.md +1 -1
- package/pipeline/multi-agent-refs/analysis/evidence.md +0 -9
- package/pipeline/multi-agent-refs/analysis/intake.md +1 -1
- package/pipeline/multi-agent-refs/analysis/locked.md +21 -22
- package/pipeline/multi-agent-refs/analysis/render.md +1 -1
- package/pipeline/multi-agent-refs/analysis/synthesis.md +12 -6
- package/pipeline/multi-agent-refs/android-guide.md +1 -1
- package/pipeline/multi-agent-refs/audit-guide.md +13 -13
- package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
- package/pipeline/multi-agent-refs/channels/jira.md +3 -3
- package/pipeline/multi-agent-refs/channels/pr.md +4 -4
- package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
- package/pipeline/multi-agent-refs/cross-cli-contract.md +31 -6
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +74 -4
- package/pipeline/multi-agent-refs/features/code-graph.md +5 -5
- package/pipeline/multi-agent-refs/features/cost-analysis.md +93 -0
- package/pipeline/multi-agent-refs/features/design-conformance.md +1 -1
- package/pipeline/multi-agent-refs/features/dev-critic.md +3 -3
- package/pipeline/multi-agent-refs/features/doctor.md +47 -2
- package/pipeline/multi-agent-refs/features/external-context-injection.md +3 -3
- package/pipeline/multi-agent-refs/features/maturity-followup.md +3 -3
- package/pipeline/multi-agent-refs/features/model-fallback.md +5 -5
- package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
- package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
- package/pipeline/multi-agent-refs/features/review-delta.md +3 -3
- package/pipeline/multi-agent-refs/features/review-multi-repo.md +1 -1
- package/pipeline/multi-agent-refs/features/scope-check.md +4 -4
- package/pipeline/multi-agent-refs/features/skill-conformance.md +2 -2
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +1 -1
- package/pipeline/multi-agent-refs/features/verify-by-test.md +4 -4
- package/pipeline/multi-agent-refs/features/verify.md +83 -0
- package/pipeline/multi-agent-refs/features/visual-evidence.md +19 -19
- package/pipeline/multi-agent-refs/features/worktree-finalize.md +6 -6
- package/pipeline/multi-agent-refs/issue-jira-triad.md +10 -10
- package/pipeline/multi-agent-refs/knowledge.md +11 -11
- package/pipeline/multi-agent-refs/multi-repo-integration-build.md +13 -13
- package/pipeline/multi-agent-refs/payload-contracts.md +8 -8
- package/pipeline/multi-agent-refs/phases/log-format.md +10 -10
- package/pipeline/multi-agent-refs/phases/modes.md +30 -30
- package/pipeline/multi-agent-refs/phases/operations.md +21 -10
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +25 -25
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +599 -0
- package/pipeline/multi-agent-refs/phases/{phase-3-dev.md → phase-2-dev.md} +129 -49
- package/pipeline/multi-agent-refs/phases/{phase-4-review.md → phase-3-review.md} +225 -107
- package/pipeline/multi-agent-refs/phases/{phase-6-commit.md → phase-4-commit.md} +23 -23
- package/pipeline/multi-agent-refs/phases/{phase-7-report.md → phase-5-report.md} +29 -29
- package/pipeline/multi-agent-refs/phases.md +44 -48
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/progress-contract.md +6 -6
- package/pipeline/multi-agent-refs/readiness-review.md +1 -1
- package/pipeline/multi-agent-refs/rules.md +7 -7
- package/pipeline/multi-agent-refs/swiftui-guide.md +2 -2
- package/pipeline/multi-agent-refs/tracker-contract.md +31 -32
- package/pipeline/multi-agent-refs/unattended-contract.md +129 -0
- package/pipeline/multi-agent-refs/wiki-capture.md +14 -14
- package/pipeline/preferences-template.json +9 -1
- package/pipeline/rules/outside-the-pipeline.md +1 -1
- package/pipeline/schemas/agent-state.schema.json +50 -50
- package/pipeline/schemas/analysis-output.schema.json +2 -2
- package/pipeline/schemas/autopilot-config.schema.json +1 -1
- package/pipeline/schemas/code-graph.schema.json +1 -1
- package/pipeline/schemas/criteria-manifest.schema.json +1 -1
- package/pipeline/schemas/dev-critic-output.schema.json +1 -1
- package/pipeline/schemas/diff-risk.schema.json +1 -1
- package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +2 -2
- package/pipeline/schemas/migrations/prefs-2.6.0-to-2.7.0.mjs +31 -0
- package/pipeline/schemas/migrations/state-2.1.0-to-2.2.0.mjs +129 -0
- package/pipeline/schemas/phases.json +105 -0
- package/pipeline/schemas/plan-todos.schema.json +5 -5
- package/pipeline/schemas/planning-output.schema.json +1 -1
- package/pipeline/schemas/prefs.schema.json +100 -56
- package/pipeline/schemas/reviewer-output.schema.json +3 -3
- package/pipeline/schemas/route-config.schema.json +74 -0
- package/pipeline/schemas/scope-check.schema.json +1 -1
- package/pipeline/schemas/test-gap.schema.json +1 -1
- package/pipeline/schemas/token-budget.json +12 -18
- package/pipeline/schemas/triage-output.schema.json +6 -6
- package/pipeline/scripts/README.md +3 -3
- package/pipeline/scripts/_code-graph.mjs +2 -2
- package/pipeline/scripts/_run-paths.mjs +372 -0
- package/pipeline/scripts/_smoke-root.sh +1 -1
- package/pipeline/scripts/aggregate-metrics.mjs +65 -65
- package/pipeline/scripts/autopilot-arming.mjs +2 -1
- package/pipeline/scripts/autopilot-intake.mjs +2 -1
- package/pipeline/scripts/autopilot-runner.mjs +206 -2
- package/pipeline/scripts/build-references.mjs +2 -1
- package/pipeline/scripts/build-stack-plugins.mjs +10 -2
- package/pipeline/scripts/capture-evidence.sh +7 -2
- package/pipeline/scripts/capture-flush.sh +8 -8
- package/pipeline/scripts/capture-resume.sh +3 -3
- package/pipeline/scripts/classify-plan-safety.mjs +3 -2
- package/pipeline/scripts/cost-analyze.mjs +600 -0
- package/pipeline/scripts/cost-budget-check.mjs +4 -12
- package/pipeline/scripts/council-view.mjs +2 -1
- package/pipeline/scripts/crush-json.mjs +2 -1
- package/pipeline/scripts/diff-explain.mjs +7 -10
- package/pipeline/scripts/diff-risk-score.mjs +2 -1
- package/pipeline/scripts/doctor.mjs +140 -6
- package/pipeline/scripts/evidence-gate.mjs +9 -3
- package/pipeline/scripts/feedback-send.mjs +12 -2
- package/pipeline/scripts/gc-abandoned.sh +32 -16
- package/pipeline/scripts/gc-tmp.sh +1 -1
- package/pipeline/scripts/gc-worktrees.sh +12 -5
- package/pipeline/scripts/gen-facts.mjs +175 -0
- package/pipeline/scripts/gen-mode-dispatch.mjs +32 -37
- package/pipeline/scripts/gen-ref-toc.mjs +1 -1
- package/pipeline/scripts/github-ssh-setup.sh +64 -7
- package/pipeline/scripts/graph-mermaid.mjs +4 -2
- package/pipeline/scripts/graph-report.mjs +1 -1
- package/pipeline/scripts/jira-attach.sh +1 -1
- package/pipeline/scripts/keychain-save.sh +101 -30
- package/pipeline/scripts/learn-from-transcripts.mjs +3 -2
- package/pipeline/scripts/learning-curve.mjs +36 -31
- package/pipeline/scripts/log-metric.sh +17 -4
- package/pipeline/scripts/make-manifest.mjs +199 -0
- package/pipeline/scripts/memory-save.sh +1 -1
- package/pipeline/scripts/migrate-prefs.mjs +24 -6
- package/pipeline/scripts/migrate-state.mjs +94 -4
- package/pipeline/scripts/phase-banner.sh +26 -22
- package/pipeline/scripts/phase-tracker.sh +48 -10
- package/pipeline/scripts/plan-coverage-gate.mjs +8 -4
- package/pipeline/scripts/pre-commit-check.sh +7 -0
- package/pipeline/scripts/pre-push-check.sh +7 -0
- package/pipeline/scripts/purge.sh +23 -6
- package/pipeline/scripts/render-agent-log-cost.sh +10 -3
- package/pipeline/scripts/render-cost-summary.sh +9 -2
- package/pipeline/scripts/render-work-summary.sh +14 -7
- package/pipeline/scripts/review-file-filter.mjs +5 -3
- package/pipeline/scripts/review-scope.mjs +2 -1
- package/pipeline/scripts/routine-registry.mjs +2 -1
- package/pipeline/scripts/run-aggregator.mjs +26 -20
- package/pipeline/scripts/run-metrics.mjs +4 -2
- package/pipeline/scripts/runs-index.mjs +353 -0
- package/pipeline/scripts/scorecard-snapshot.mjs +178 -0
- package/pipeline/scripts/search-logs.sh +18 -0
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +6 -6
- package/pipeline/scripts/smoke-schema-validation.sh +26 -7
- package/pipeline/scripts/test-gap-scan.mjs +2 -1
- package/pipeline/scripts/test-integrity-gate.mjs +2 -1
- package/pipeline/scripts/token-budget-report.mjs +13 -2
- package/pipeline/scripts/triage-memory.mjs +2 -2
- package/pipeline/scripts/update-issue-progress.sh +56 -7
- package/pipeline/scripts/usage-report.mjs +12 -1
- package/pipeline/scripts/validate-analysis-doc.mjs +75 -18
- package/pipeline/scripts/validate-code-graph.mjs +6 -3
- package/pipeline/scripts/validate-complaint-doc.mjs +2 -1
- package/pipeline/scripts/validate-diff-risk.mjs +6 -3
- package/pipeline/scripts/validate-planning.mjs +1 -1
- package/pipeline/scripts/validate-reviewer.mjs +1 -1
- package/pipeline/scripts/validate-state.mjs +45 -5
- package/pipeline/scripts/validate-test-gap.mjs +6 -3
- package/pipeline/scripts/validate-triage.mjs +6 -4
- package/pipeline/scripts/verify-citations.mjs +4 -2
- package/pipeline/scripts/verify.mjs +327 -0
- package/pipeline/scripts/worktree-finalize.sh +18 -9
- package/pipeline/scripts/write-state.mjs +154 -15
- package/pipeline/skills/.skill-manifest.json +37 -21
- package/pipeline/skills/.skills-index.json +104 -5
- package/pipeline/skills/shared/README.md +15 -6
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +2 -2
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +69 -71
- package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +14 -14
- package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +5 -5
- package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +25 -23
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +6 -6
- package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +71 -0
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-resume-local/SKILL.md +7 -7
- package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +39 -0
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +76 -0
- package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +59 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +35 -11
- package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +6 -5
- package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/package_app.sh +4 -1
- package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh +4 -1
- package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh +2 -1
- package/pipeline/skills/skills-index.md +13 -4
- package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +0 -263
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +0 -344
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +0 -182
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import fs from "node:fs";
|
|
22
22
|
import os from "node:os";
|
|
23
23
|
import path from "node:path";
|
|
24
|
+
import { rowPhase } from "../lib/phase-schema.mjs";
|
|
24
25
|
|
|
25
26
|
const args = parseArgs(process.argv.slice(2));
|
|
26
27
|
const METRICS =
|
|
@@ -64,7 +65,12 @@ for (const e of events) {
|
|
|
64
65
|
models: new Set(),
|
|
65
66
|
});
|
|
66
67
|
const t = byTask.get(e.task_id);
|
|
67
|
-
|
|
68
|
+
// Normalised to the current vocabulary before it goes in the set. This log
|
|
69
|
+
// is append-only across the v19.0.0 renumbering, so the raw field carries
|
|
70
|
+
// two different meanings and a set of raw values cannot be compared to
|
|
71
|
+
// anything.
|
|
72
|
+
const cp = rowPhase(e);
|
|
73
|
+
if (cp !== null) t.phases.add(cp);
|
|
68
74
|
const d = e.details || {};
|
|
69
75
|
if (typeof d.tokens_in === "number") t.tokens_in += d.tokens_in;
|
|
70
76
|
if (typeof d.tokens_out === "number") t.tokens_out += d.tokens_out;
|
|
@@ -113,7 +119,12 @@ for (const [taskId, t] of byTask) {
|
|
|
113
119
|
summary.totalTokensOut += t.tokens_out;
|
|
114
120
|
summary.totalUsd += usd;
|
|
115
121
|
const total = t.tokens_in + t.tokens_out;
|
|
116
|
-
|
|
122
|
+
// Heuristic: a task that reached Review with many premium calls is a
|
|
123
|
+
// multi-repo run. Keyed on Review (3), not on the literal "4" this line used
|
|
124
|
+
// to carry - under the six-phase contract 4 is Commit, and every run reaches
|
|
125
|
+
// Commit, so the old literal would have classified nearly everything as
|
|
126
|
+
// multi-repo and applied the wrong ceiling.
|
|
127
|
+
const isMulti = t.phases.has(3) && t.premium_calls > 5;
|
|
117
128
|
const tokenLimit = isMulti ? limits.perTaskTokensMultiRepo : limits.perTaskTokens;
|
|
118
129
|
const usdLimit = isMulti ? limits.perTaskUsdMultiRepo : limits.perTaskUsd;
|
|
119
130
|
if (total > tokenLimit)
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file triage-memory.mjs - v8.3.0
|
|
5
5
|
*
|
|
6
|
-
* Lightweight, zero-dep persistence layer for past Phase
|
|
7
|
-
* Used by Phase
|
|
6
|
+
* Lightweight, zero-dep persistence layer for past Phase 3 triage findings.
|
|
7
|
+
* Used by Phase 5 to ingest results, by Phase 1/4 to look up prior art when
|
|
8
8
|
* a new task overlaps with a previous one, and by /multi-agent:search.
|
|
9
9
|
*
|
|
10
10
|
* Storage: append-only JSONL, one file per repo:
|
|
@@ -29,6 +29,54 @@
|
|
|
29
29
|
|
|
30
30
|
set -euo pipefail
|
|
31
31
|
|
|
32
|
+
# jq is not optional on this path. Without the guard below a missing binary
|
|
33
|
+
# renders as EMPTY DATA and the work continues on it; see lib/require-jq.sh.
|
|
34
|
+
for _rq in "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/require-jq.sh" \
|
|
35
|
+
"$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/../lib" 2>/dev/null && pwd)/require-jq.sh" \
|
|
36
|
+
"$HOME/.claude/lib/require-jq.sh" \
|
|
37
|
+
"$HOME/.copilot/lib/require-jq.sh" \
|
|
38
|
+
"$HOME/.codex/lib/require-jq.sh"; do
|
|
39
|
+
[ -f "$_rq" ] || continue
|
|
40
|
+
# shellcheck source=/dev/null
|
|
41
|
+
. "$_rq" && break
|
|
42
|
+
done
|
|
43
|
+
unset _rq
|
|
44
|
+
if ! command -v ma_require_jq >/dev/null 2>&1; then
|
|
45
|
+
# The helper itself is missing, which is an install problem, not a jq one.
|
|
46
|
+
ma_require_jq() { command -v jq >/dev/null 2>&1 || { echo "jq not found - cannot ${1:-continue}." >&2; return 1; }; }
|
|
47
|
+
fi
|
|
48
|
+
ma_require_jq "update the issue" || exit 3
|
|
49
|
+
|
|
50
|
+
# Outbound leak gate. Every byte below is composed at runtime out of command
|
|
51
|
+
# output, error text and file excerpts, any of which can carry a token that was
|
|
52
|
+
# on this machine a second earlier - and once it is in a comment it is in
|
|
53
|
+
# someone else's database. The repo's own leak scanner looks at FILES IN THE
|
|
54
|
+
# REPO and never sees this text. See lib/outbound-gate.mjs.
|
|
55
|
+
ma_outbound_gate() {
|
|
56
|
+
local body_file="$1" og=""
|
|
57
|
+
for c in "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/outbound-gate.mjs" \
|
|
58
|
+
"$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/../lib" 2>/dev/null && pwd)/outbound-gate.mjs" \
|
|
59
|
+
"$HOME/.claude/lib/outbound-gate.mjs" \
|
|
60
|
+
"$HOME/.copilot/lib/outbound-gate.mjs" \
|
|
61
|
+
"$HOME/.codex/lib/outbound-gate.mjs"; do
|
|
62
|
+
[ -f "$c" ] && { og="$c"; break; }
|
|
63
|
+
done
|
|
64
|
+
# Missing gate is NOT an open door: refusing to publish beats publishing
|
|
65
|
+
# unchecked, and the only way this file is absent is a broken install.
|
|
66
|
+
if [ -z "$og" ]; then
|
|
67
|
+
echo "outbound-gate.mjs not found - refusing to publish unchecked text." >&2
|
|
68
|
+
return 7
|
|
69
|
+
fi
|
|
70
|
+
node "$og" --file "$body_file"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Run-state path resolution: pipeline/lib/run-paths.sh owns the two layouts
|
|
74
|
+
# (nested <root>/<project>/<id>/ and flat <root>/<id>/) and every id spelling.
|
|
75
|
+
_MA_RP_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
76
|
+
# shellcheck source=/dev/null
|
|
77
|
+
. "$_MA_RP_HERE/../lib/run-paths.sh" 2>/dev/null || . "$HOME/.claude/lib/run-paths.sh"
|
|
78
|
+
|
|
79
|
+
|
|
32
80
|
# Rewrite the `### Progress` block of the body file ($2) with $1 and print the
|
|
33
81
|
# result to stdout. The replaced region is bounded at the FIRST of:
|
|
34
82
|
# - a line starting with `<!--` (the legend comment, printed and kept), or
|
|
@@ -90,13 +138,10 @@ fi
|
|
|
90
138
|
# per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
|
|
91
139
|
# - not the ~/.claude/projects/*/state/ path this used to glob, which no
|
|
92
140
|
# writer in the pipeline ever populates.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
break
|
|
98
|
-
fi
|
|
99
|
-
done
|
|
141
|
+
# This globbed ONLY the nested layout. phase-tracker.sh and most Phase 0 paths
|
|
142
|
+
# write flat, so on a real machine the majority of runs resolved to nothing and
|
|
143
|
+
# this script reported "no agent-state.json" for a run whose state existed.
|
|
144
|
+
AGENT_STATE="$(ma_resolve_run_file "$TASK_ID" agent-state.json 2>/dev/null || true)"
|
|
100
145
|
|
|
101
146
|
if [ -z "$AGENT_STATE" ]; then
|
|
102
147
|
echo "update-issue-progress: no agent-state.json found for task=$TASK_ID" >&2
|
|
@@ -154,6 +199,10 @@ if cmp -s "$TMP_BODY" "$TMP_NEW"; then
|
|
|
154
199
|
fi
|
|
155
200
|
|
|
156
201
|
# Apply.
|
|
202
|
+
if ! ma_outbound_gate "$TMP_NEW"; then
|
|
203
|
+
echo "update-issue-progress: outbound gate refused the new body; #$ISSUE_NUM left unchanged" >&2
|
|
204
|
+
exit 7
|
|
205
|
+
fi
|
|
157
206
|
if ! gh issue edit "$ISSUE_NUM" --repo "$ORG_REPO" --body-file "$TMP_NEW" >/dev/null; then
|
|
158
207
|
echo "update-issue-progress: gh issue edit failed for #$ISSUE_NUM" >&2
|
|
159
208
|
exit 4
|
|
@@ -609,7 +609,18 @@ async function main() {
|
|
|
609
609
|
// Windows-safe entry-point check: compare file URLs, never string-split a path.
|
|
610
610
|
const invokedDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
611
611
|
if (invokedDirectly) {
|
|
612
|
-
|
|
612
|
+
// Exit 0 stays: usage telemetry must never be the reason a run fails, and a
|
|
613
|
+
// non-zero exit here would propagate into whatever called it.
|
|
614
|
+
//
|
|
615
|
+
// But the silence goes. `catch(() => process.exit(0))` reported success for
|
|
616
|
+
// every failure, so a telemetry path that had been broken for weeks looked
|
|
617
|
+
// exactly like one that worked - there was no surface on which anyone could
|
|
618
|
+
// notice. One line on stderr costs nothing and is the difference between a
|
|
619
|
+
// degraded feature and an invisible one.
|
|
620
|
+
main().catch((err) => {
|
|
621
|
+
process.stderr.write(`usage-report: not sent - ${err?.message ?? err}\n`);
|
|
622
|
+
process.exit(0);
|
|
623
|
+
});
|
|
613
624
|
}
|
|
614
625
|
|
|
615
626
|
export {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Checks (ERROR = blocking exit 1; WARN = advisory, still exit 0 unless --strict):
|
|
13
13
|
// - Front-matter block with required keys (feature, platform, language, mode,
|
|
14
|
-
// template_version); mode
|
|
14
|
+
// template_version); mode must be full (lite removed in v19.0.0); platform in the known set.
|
|
15
15
|
// - Never-omitted sections present by title keyword (Locked 2): Summary,
|
|
16
16
|
// Goals, User Stories, API Contracts, Architecture, Files to Add, Risks,
|
|
17
17
|
// References.
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
// Exit: 0 valid, 1 invalid (or WARN under --strict), 64 usage error.
|
|
42
42
|
|
|
43
43
|
import { readFileSync } from "node:fs";
|
|
44
|
+
import { runMain } from "../lib/fatal.mjs";
|
|
44
45
|
|
|
45
46
|
// A repo-less run (Locked 35) still splits by channel, derived from the evidence:
|
|
46
47
|
// "mobile" and "web" are its platform values. "none" is the narrower case where the
|
|
@@ -244,16 +245,31 @@ function main() {
|
|
|
244
245
|
for (const k of REQUIRED_FM) {
|
|
245
246
|
if (!parsed.fm[k]) errors.push(`front-matter missing required key: ${k}`);
|
|
246
247
|
}
|
|
248
|
+
// `mode` is accepted only as "full" now. Lite was removed in v19.0.0: it
|
|
249
|
+
// picked sections from a fixed list chosen by a three-signal score, which
|
|
250
|
+
// fought Locked 2 in both directions - a small feature with rich business
|
|
251
|
+
// rules lost Section 15 because it was not on the list, and a feature with
|
|
252
|
+
// no API contract kept Section 9 because it was. An older document carrying
|
|
253
|
+
// `mode: lite` is still readable; it is reported as a legacy value rather
|
|
254
|
+
// than rejected, because the document was valid when it was written.
|
|
247
255
|
const mode = parsed.fm.mode;
|
|
248
|
-
if (mode
|
|
249
|
-
|
|
256
|
+
if (mode === "lite") {
|
|
257
|
+
warns.push(
|
|
258
|
+
"front-matter says mode: lite - Lite was removed in v19.0.0 and this is a pre-v19 document. Sections are chosen by evidence now (Locked 2).",
|
|
259
|
+
);
|
|
260
|
+
} else if (mode && mode !== "full") {
|
|
261
|
+
errors.push(`front-matter mode must be full, got: ${mode}`);
|
|
250
262
|
}
|
|
251
263
|
const plat = parsed.fm.platform;
|
|
252
264
|
if (plat && !KNOWN_PLATFORMS.has(plat)) {
|
|
253
265
|
errors.push(`front-matter platform not in known set: ${plat}`);
|
|
254
266
|
}
|
|
255
267
|
}
|
|
256
|
-
|
|
268
|
+
// A pre-v19 `lite` document is validated as Full: the checks it skipped were
|
|
269
|
+
// skipped because of a mode that no longer exists, and re-skipping them here
|
|
270
|
+
// would carry that exemption forward forever.
|
|
271
|
+
const mode = "full";
|
|
272
|
+
void parsed?.fm?.mode;
|
|
257
273
|
const profile = (parsed?.fm?.profile || "global").toLowerCase();
|
|
258
274
|
if (profile !== "global" && profile !== "corporate") {
|
|
259
275
|
errors.push(`front-matter profile must be global|corporate, got: ${profile}`);
|
|
@@ -384,7 +400,7 @@ function main() {
|
|
|
384
400
|
} else if (rows.some((l) => /\bAS-\d{2,3}\b/.test(l))) {
|
|
385
401
|
// Only when there is something to own. A Section 20 with no stamped row
|
|
386
402
|
// has no owner to check, and warning there would fire on every document
|
|
387
|
-
// that never needed the column - noise that Phase
|
|
403
|
+
// that never needed the column - noise that Phase 3 runs under --strict.
|
|
388
404
|
warns.push(
|
|
389
405
|
"Risks and Open Questions carries AS-NN rows but no owner column; ownership cannot be checked",
|
|
390
406
|
);
|
|
@@ -504,7 +520,7 @@ function main() {
|
|
|
504
520
|
}
|
|
505
521
|
mark("traceability-matrix");
|
|
506
522
|
} else {
|
|
507
|
-
mark("traceability-matrix"
|
|
523
|
+
mark("traceability-matrix");
|
|
508
524
|
}
|
|
509
525
|
} else {
|
|
510
526
|
mark("payload-in-table-cell", "global profile");
|
|
@@ -579,7 +595,7 @@ function main() {
|
|
|
579
595
|
|
|
580
596
|
mark("optin-coverage-sections");
|
|
581
597
|
|
|
582
|
-
// 2c. The Test Plan is Phase 3's RED input (phase-
|
|
598
|
+
// 2c. The Test Plan is Phase 3's RED input (phase-2-dev.md step 5b): the tests
|
|
583
599
|
// written first come from it. Full mode without it hands dev an empty matrix.
|
|
584
600
|
// It is Section 15 in the global profile and Section 19 in the corporate one;
|
|
585
601
|
// the check matches on the title, so only the stack-optional case needs an
|
|
@@ -593,7 +609,7 @@ function main() {
|
|
|
593
609
|
// The omission table allows exactly one case: a backend-only run with no
|
|
594
610
|
// contract testing planned. The validator knows the platform, so that case
|
|
595
611
|
// warns instead of blocking; every other platform hands dev an empty matrix.
|
|
596
|
-
const msg = "no Section 15 Test Plan;
|
|
612
|
+
const msg = "no Section 15 Test Plan; dev reads it as the RED input (Locked 30)";
|
|
597
613
|
if (parsed?.fm?.platform === "backend") {
|
|
598
614
|
warns.push(`${msg} - allowed for backend only when no contract testing is planned`);
|
|
599
615
|
} else {
|
|
@@ -614,8 +630,7 @@ function main() {
|
|
|
614
630
|
}
|
|
615
631
|
}
|
|
616
632
|
|
|
617
|
-
if (
|
|
618
|
-
else if (noPlatform) mark("test-plan", "no target platform");
|
|
633
|
+
if (noPlatform) mark("test-plan", "no target platform");
|
|
619
634
|
else mark("test-plan");
|
|
620
635
|
|
|
621
636
|
// 2d. Section 3 carries the diagrams. Omission is legitimate under the
|
|
@@ -725,7 +740,7 @@ function main() {
|
|
|
725
740
|
|
|
726
741
|
mark("concept-table-evidence", conceptBody ? undefined : "no concept table");
|
|
727
742
|
|
|
728
|
-
// 2g. Locked
|
|
743
|
+
// 2g. Locked 30 main clause: a rule with no test scenario fails the gate.
|
|
729
744
|
// The pre-existing occurrence-count heuristic below stays as the looser net.
|
|
730
745
|
if (mode === "full") {
|
|
731
746
|
const idRx = /BR-[a-z0-9]+(?:-[a-z0-9]+)*-\d+/gi;
|
|
@@ -746,7 +761,7 @@ function main() {
|
|
|
746
761
|
}
|
|
747
762
|
}
|
|
748
763
|
|
|
749
|
-
mark("rule-to-test"
|
|
764
|
+
mark("rule-to-test");
|
|
750
765
|
|
|
751
766
|
// 2h. Redesign mode (Locked 37). Eight checks, live only when the front-matter
|
|
752
767
|
// opts in. The contract they enforce is analysis/redesign.md, which loads only
|
|
@@ -793,7 +808,7 @@ function main() {
|
|
|
793
808
|
);
|
|
794
809
|
}
|
|
795
810
|
// 3. A rule that lives only in a shared module legitimately reads this way,
|
|
796
|
-
// so it warns rather than blocks - and Phase
|
|
811
|
+
// so it warns rather than blocks - and Phase 3 runs --strict, so it blocks
|
|
797
812
|
// there, which is where a human is already looking.
|
|
798
813
|
if (/cross-cutting/i.test(evidence) && certainty.includes("confirmed")) {
|
|
799
814
|
warns.push(
|
|
@@ -870,7 +885,7 @@ function main() {
|
|
|
870
885
|
// keyword as a raw substring of the heading line - deliberately, since Locked
|
|
871
886
|
// 2 re-flows section numbers and the corporate profile numbers them
|
|
872
887
|
// differently - so an unrelated `### 4.9 Notes on the old Current Behaviour
|
|
873
|
-
// audit process` matched, and Phase
|
|
888
|
+
// audit process` matched, and Phase 3 runs --strict, which turns this warning
|
|
874
889
|
// into a blocked document. The artefact itself is the honest signal: a
|
|
875
890
|
// rendered current-behaviour table always carries `CB-<slug>-NN` ids, and a
|
|
876
891
|
// paragraph that merely uses the words never does.
|
|
@@ -920,7 +935,49 @@ function main() {
|
|
|
920
935
|
|
|
921
936
|
mark("placeholder-bodies", profile === "global" ? undefined : "corporate profile");
|
|
922
937
|
|
|
923
|
-
//
|
|
938
|
+
// 4b. Locked 2's numbering half, which was ungated until v19.0.0.
|
|
939
|
+
//
|
|
940
|
+
// It is NOT "numbering re-flows 1..N". That is what the rule used to say and
|
|
941
|
+
// it could not hold: Locked 30 threads ids across the document by section
|
|
942
|
+
// NUMBER ("Section 15.1 scenario", "Section 4.4", "Section 3/5 layout cells"),
|
|
943
|
+
// so a re-flowed document sends every one of those references to the wrong
|
|
944
|
+
// section. No emitted document ever re-flowed. The rule was the wrong half.
|
|
945
|
+
//
|
|
946
|
+
// What holds instead: a rendered section keeps its canonical template number,
|
|
947
|
+
// so gaps are correct and `1, 2, 4, 9, 13, 14, 21` is a valid document. Two
|
|
948
|
+
// things are still defects and both are checked here - a number outside the
|
|
949
|
+
// template's range, and numbers that repeat or go backwards. Either one means
|
|
950
|
+
// a cross-reference lands somewhere its author did not intend.
|
|
951
|
+
{
|
|
952
|
+
const MAX_SECTION = 23;
|
|
953
|
+
const nums = [];
|
|
954
|
+
for (const l of text.split("\n")) {
|
|
955
|
+
const m = /^##\s+(\d+)\.\s/.exec(l);
|
|
956
|
+
if (m) nums.push(Number(m[1]));
|
|
957
|
+
}
|
|
958
|
+
if (nums.length === 0) {
|
|
959
|
+
mark("section-numbering", "no numbered top-level sections");
|
|
960
|
+
} else {
|
|
961
|
+
const outOfRange = nums.filter((n) => n < 1 || n > MAX_SECTION);
|
|
962
|
+
const notAscending = [];
|
|
963
|
+
for (let i = 1; i < nums.length; i++) {
|
|
964
|
+
if (nums[i] <= nums[i - 1]) notAscending.push(`${nums[i - 1]} then ${nums[i]}`);
|
|
965
|
+
}
|
|
966
|
+
if (outOfRange.length > 0) {
|
|
967
|
+
errors.push(
|
|
968
|
+
`section number outside the 1..${MAX_SECTION} template range (Locked 2): ${outOfRange.join(", ")}`,
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
if (notAscending.length > 0) {
|
|
972
|
+
errors.push(
|
|
973
|
+
`section numbers repeat or go backwards (Locked 2): ${notAscending.slice(0, 4).join("; ")}`,
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
mark("section-numbering");
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// 5. Traceability (Locked 30)
|
|
924
981
|
if (mode === "full") {
|
|
925
982
|
const idRe = /BR-[a-z0-9]+(?:-[a-z0-9]+)*-\d+/gi;
|
|
926
983
|
const all = text.match(idRe) || [];
|
|
@@ -930,7 +987,7 @@ function main() {
|
|
|
930
987
|
counts.set(key, (counts.get(key) || 0) + 1);
|
|
931
988
|
}
|
|
932
989
|
if (counts.size === 0 && profile === "global") {
|
|
933
|
-
warns.push("
|
|
990
|
+
warns.push("no BR-<slug>-NN business-rule ids found (Section 4.4 / Locked 30)");
|
|
934
991
|
}
|
|
935
992
|
for (const [id, n] of counts) {
|
|
936
993
|
if (n < 2) {
|
|
@@ -941,7 +998,7 @@ function main() {
|
|
|
941
998
|
}
|
|
942
999
|
}
|
|
943
1000
|
|
|
944
|
-
mark("br-reference-count"
|
|
1001
|
+
mark("br-reference-count");
|
|
945
1002
|
|
|
946
1003
|
// 6. Manual test scenarios (Section 15.7): every MT row carries a BR id.
|
|
947
1004
|
// A manual scenario nobody can trace back to a rule is a scenario nobody can
|
|
@@ -1019,4 +1076,4 @@ function main() {
|
|
|
1019
1076
|
console.log(`validate-analysis-doc: OK (mode=${mode}, ${warns.length} warning(s))`);
|
|
1020
1077
|
}
|
|
1021
1078
|
|
|
1022
|
-
main
|
|
1079
|
+
runMain("validate-analysis-doc", main);
|
|
@@ -162,13 +162,16 @@ export function validate(obj) {
|
|
|
162
162
|
obj = JSON.parse(raw);
|
|
163
163
|
} catch (e) {
|
|
164
164
|
console.log(JSON.stringify({ ok: false, errors: [`JSON parse error: ${e.message}`] }));
|
|
165
|
-
process.
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
return;
|
|
166
167
|
}
|
|
167
168
|
const errors = validate(obj);
|
|
168
169
|
if (errors.length === 0) {
|
|
169
170
|
console.log(JSON.stringify({ ok: true, errors: [] }));
|
|
170
|
-
|
|
171
|
+
return;
|
|
171
172
|
}
|
|
173
|
+
// `errors` is one entry per violation and has no bound, so process.exit(1)
|
|
174
|
+
// here truncated the report exactly when there was most of it to read.
|
|
172
175
|
console.log(JSON.stringify({ ok: false, errors }));
|
|
173
|
-
process.
|
|
176
|
+
process.exitCode = 1;
|
|
174
177
|
})();
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
// Exit: 0 valid, 1 invalid (or WARN under --strict), 64 usage error.
|
|
41
41
|
|
|
42
42
|
import { readFileSync } from "node:fs";
|
|
43
|
+
import { runMain } from "../lib/fatal.mjs";
|
|
43
44
|
|
|
44
45
|
const REQUIRED_FM = ["run_name", "generated_at", "language", "complaint_count", "graylog_degraded"];
|
|
45
46
|
|
|
@@ -263,4 +264,4 @@ function main() {
|
|
|
263
264
|
process.exit(0);
|
|
264
265
|
}
|
|
265
266
|
|
|
266
|
-
main
|
|
267
|
+
runMain("validate-complaint-doc", main);
|
|
@@ -120,13 +120,16 @@ function validate(obj) {
|
|
|
120
120
|
obj = JSON.parse(raw);
|
|
121
121
|
} catch (e) {
|
|
122
122
|
console.log(JSON.stringify({ ok: false, errors: [`JSON parse error: ${e.message}`] }));
|
|
123
|
-
process.
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
return;
|
|
124
125
|
}
|
|
125
126
|
const errors = validate(obj);
|
|
126
127
|
if (errors.length === 0) {
|
|
127
128
|
console.log(JSON.stringify({ ok: true, errors: [] }));
|
|
128
|
-
|
|
129
|
+
return;
|
|
129
130
|
}
|
|
131
|
+
// `errors` is one entry per violation and has no bound, so process.exit(1)
|
|
132
|
+
// here truncated the report exactly when there was most of it to read.
|
|
130
133
|
console.log(JSON.stringify({ ok: false, errors }));
|
|
131
|
-
process.
|
|
134
|
+
process.exitCode = 1;
|
|
132
135
|
})();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// validate-planning.mjs - runtime validator for Phase
|
|
2
|
+
// validate-planning.mjs - runtime validator for Phase 1 planning output.
|
|
3
3
|
//
|
|
4
4
|
// Phase 2 returns a task decomposition conforming to
|
|
5
5
|
// pipeline/schemas/planning-output.schema.json. Phase 3 iterates over `tasks`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// validate-reviewer.mjs - runtime validator for Phase
|
|
2
|
+
// validate-reviewer.mjs - runtime validator for Phase 3 reviewer subagent output.
|
|
3
3
|
//
|
|
4
4
|
// Each of the 3 parallel reviewers (Opus, GPT-5.4, Sonnet) must return an
|
|
5
5
|
// object matching pipeline/schemas/reviewer-output.schema.json. This validator
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// actually make a resume unsafe:
|
|
15
15
|
// - the file does not parse (truncated / corrupt)
|
|
16
16
|
// - root is not an object
|
|
17
|
-
// - currentPhase is missing or out of the 0..
|
|
17
|
+
// - currentPhase is missing or out of the 0..5 range (resume keys on it)
|
|
18
18
|
// - phases is present but not an object, or an entry is not an object
|
|
19
19
|
// Phase status strings are NOT enum-checked: real writers use "completed"
|
|
20
20
|
// alongside the schema's "done", and an odd status does not make a resume
|
|
@@ -66,9 +66,33 @@ async function main() {
|
|
|
66
66
|
process.exit(1);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// Which numbering this file was written under. v19.0.0 renumbered eight
|
|
70
|
+
// phases into six, and a run that finished at `currentPhase: 7` in October
|
|
71
|
+
// was correct when it was written - calling it invalid now would mark most
|
|
72
|
+
// of the log directory broken and teach the reader to ignore this validator.
|
|
73
|
+
// Same decision the metrics file got: label the generation, do not rewrite
|
|
74
|
+
// history. A file stamped 2.2.0 or later is generation 2 and bounded 0..5;
|
|
75
|
+
// anything older, including the unstamped files that predate stamping
|
|
76
|
+
// entirely, is generation 1 and bounded 0..7.
|
|
77
|
+
const GEN2_FROM = "2.2.0";
|
|
78
|
+
const cmpVersion = (a, b) => {
|
|
79
|
+
const pa = String(a).split(".").map(Number);
|
|
80
|
+
const pb = String(b).split(".").map(Number);
|
|
81
|
+
for (let i = 0; i < 3; i++) {
|
|
82
|
+
if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
};
|
|
86
|
+
const gen1 = !s.schemaVersion || cmpVersion(s.schemaVersion, GEN2_FROM) < 0;
|
|
87
|
+
const maxPhase = gen1 ? 7 : 5;
|
|
88
|
+
const phaseKey = gen1 ? /^[0-7]$/ : /^[0-5]$/;
|
|
89
|
+
|
|
69
90
|
// currentPhase is the field resume keys on - it must be present and in range.
|
|
70
|
-
if (!Number.isInteger(s.currentPhase) || s.currentPhase < 0 || s.currentPhase >
|
|
71
|
-
errors.push(
|
|
91
|
+
if (!Number.isInteger(s.currentPhase) || s.currentPhase < 0 || s.currentPhase > maxPhase) {
|
|
92
|
+
errors.push(
|
|
93
|
+
`currentPhase must be integer 0..${maxPhase}, got ${JSON.stringify(s.currentPhase)}` +
|
|
94
|
+
(gen1 ? " (generation 1 file: unstamped or schemaVersion < 2.2.0)" : ""),
|
|
95
|
+
);
|
|
72
96
|
}
|
|
73
97
|
|
|
74
98
|
// phases is optional (some early/aborted states omit it), but if present it
|
|
@@ -78,12 +102,28 @@ async function main() {
|
|
|
78
102
|
errors.push("phases, when present, must be an object keyed by phase number");
|
|
79
103
|
} else {
|
|
80
104
|
for (const [k, v] of Object.entries(s.phases)) {
|
|
81
|
-
if (
|
|
82
|
-
errors.push(`phases key invalid: "${k}" (expected "0".."
|
|
105
|
+
if (!phaseKey.test(k)) {
|
|
106
|
+
errors.push(`phases key invalid: "${k}" (expected "0".."${maxPhase}")`);
|
|
83
107
|
continue;
|
|
84
108
|
}
|
|
85
109
|
if (typeof v !== "object" || v === null) {
|
|
86
110
|
errors.push(`phases["${k}"] must be an object`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// The rework cap. Every phase doc says "hard-kill at retryCount === 3"
|
|
114
|
+
// and the schema writes `maximum: 3`, but nothing read either: a fourth
|
|
115
|
+
// round produced a state that violates the schema and passed this
|
|
116
|
+
// validator, which is the one place that could have said so.
|
|
117
|
+
//
|
|
118
|
+
// What this does NOT do is stop the loop - that is the orchestrator's
|
|
119
|
+
// job and it stays prose. What it does is make an over-cap state fail
|
|
120
|
+
// instead of being carried forward as if it were fine.
|
|
121
|
+
if (v.retryCount !== undefined) {
|
|
122
|
+
if (!Number.isInteger(v.retryCount) || v.retryCount < 0 || v.retryCount > 3) {
|
|
123
|
+
errors.push(
|
|
124
|
+
`phases["${k}"].retryCount must be integer 0..3 (the rework cap), got ${JSON.stringify(v.retryCount)}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
87
127
|
}
|
|
88
128
|
}
|
|
89
129
|
}
|
|
@@ -88,13 +88,16 @@ function validate(obj) {
|
|
|
88
88
|
obj = JSON.parse(raw);
|
|
89
89
|
} catch (e) {
|
|
90
90
|
console.log(JSON.stringify({ ok: false, errors: [`JSON parse error: ${e.message}`] }));
|
|
91
|
-
process.
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
return;
|
|
92
93
|
}
|
|
93
94
|
const errors = validate(obj);
|
|
94
95
|
if (errors.length === 0) {
|
|
95
96
|
console.log(JSON.stringify({ ok: true, errors: [] }));
|
|
96
|
-
|
|
97
|
+
return;
|
|
97
98
|
}
|
|
99
|
+
// `errors` is one entry per violation and has no bound, so process.exit(1)
|
|
100
|
+
// here truncated the report exactly when there was most of it to read.
|
|
98
101
|
console.log(JSON.stringify({ ok: false, errors }));
|
|
99
|
-
process.
|
|
102
|
+
process.exitCode = 1;
|
|
100
103
|
})();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// validate-triage.mjs - behavioral validator for Phase
|
|
2
|
+
// validate-triage.mjs - behavioral validator for Phase 3 triage output.
|
|
3
3
|
//
|
|
4
4
|
// Zero dependencies. Reads a triage JSON from a path arg or STDIN, enforces
|
|
5
5
|
// the contract documented in pipeline/schemas/triage-output.schema.json AND
|
|
6
|
-
// the edge-case rules from phase-
|
|
6
|
+
// the edge-case rules from phase-3-review.md section3.3.
|
|
7
7
|
//
|
|
8
8
|
// Unlike validate-schemas.mjs (which only checks schema files are well-formed),
|
|
9
9
|
// this validates an ACTUAL triage run's output.
|
|
@@ -81,7 +81,7 @@ function validateRawFinding(f, label, errors) {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
// v3.2.0 verify-by-test outcome (Phase
|
|
84
|
+
// v3.2.0 verify-by-test outcome (Phase 3 Step 3.7). Optional; when present:
|
|
85
85
|
// result is required, and confirmed/not-reproduced additionally require
|
|
86
86
|
// testRef + evidencePath (the empirical claims must be traceable).
|
|
87
87
|
function validateVerification(v, label, errors) {
|
|
@@ -292,4 +292,6 @@ function validate(raw) {
|
|
|
292
292
|
const raw = await readInput();
|
|
293
293
|
const result = validate(raw);
|
|
294
294
|
console.log(JSON.stringify(result, null, 2));
|
|
295
|
-
process.exit(
|
|
295
|
+
// Assigned rather than called: process.exit() would cut this payload short when
|
|
296
|
+
// stdout is a pipe, which is how every caller reads it.
|
|
297
|
+
process.exitCode = result.ok ? result.code : 1;
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
46
46
|
import { resolve as resolvePath, sep } from "node:path";
|
|
47
47
|
import { spawnSync } from "node:child_process";
|
|
48
|
+
import { runMain } from "../lib/fatal.mjs";
|
|
49
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
48
50
|
|
|
49
51
|
/**
|
|
50
52
|
* Reject a path before it reaches git.
|
|
@@ -423,6 +425,6 @@ function main() {
|
|
|
423
425
|
process.exit(report.unresolved.length === 0 ? 0 : 1);
|
|
424
426
|
}
|
|
425
427
|
|
|
426
|
-
if (import.meta.url
|
|
427
|
-
main
|
|
428
|
+
if (invokedDirectly(import.meta.url)) {
|
|
429
|
+
runMain("verify-citations", main);
|
|
428
430
|
}
|