@mmerterden/multi-agent-pipeline 18.0.0 → 19.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/CHANGELOG.md +287 -0
- package/README.md +36 -20
- package/README.tr.md +14 -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/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 +46 -27
- package/docs/engineering.md +1 -1
- package/docs/facts.json +61 -0
- package/docs/features.md +55 -54
- package/docs/performance.md +5 -5
- package/docs/recovery-guide.md +17 -17
- package/docs/token-budget-history.md +3 -1
- package/index.js +2 -2
- package/install/_codex-agents.mjs +1 -1
- 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 +234 -216
- package/package.json +2 -2
- package/pipeline/agents/dev-critic.md +7 -7
- package/pipeline/commands/figma-to-swiftui.md +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/analysis/SKILL.md +15 -15
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +2 -2
- 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 +2 -2
- package/pipeline/commands/multi-agent/review-analysis/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 +5 -5
- 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/credential-inventory.sh +1 -1
- package/pipeline/lib/fetch-fortify.sh +1 -1
- package/pipeline/lib/model-dispatch.sh +140 -0
- package/pipeline/lib/model-rung.sh +142 -0
- package/pipeline/lib/outbound-gate.mjs +14 -0
- package/pipeline/lib/phase-schema.mjs +88 -0
- package/pipeline/lib/plan-todos.sh +5 -5
- package/pipeline/lib/route-state.sh +161 -0
- package/pipeline/lib/run-paths.sh +2 -2
- package/pipeline/multi-agent-refs/_account-picker.md +1 -1
- package/pipeline/multi-agent-refs/_dev-context.md +6 -6
- package/pipeline/multi-agent-refs/_input-parser.md +1 -1
- package/pipeline/multi-agent-refs/analysis/evidence.md +2 -11
- package/pipeline/multi-agent-refs/analysis/intake.md +7 -7
- package/pipeline/multi-agent-refs/analysis/locked.md +48 -22
- package/pipeline/multi-agent-refs/analysis/redesign.md +1 -1
- package/pipeline/multi-agent-refs/analysis/render.md +10 -10
- package/pipeline/multi-agent-refs/analysis/resolve.md +1 -1
- package/pipeline/multi-agent-refs/analysis/review.md +2 -2
- package/pipeline/multi-agent-refs/analysis/synthesis.md +13 -7
- package/pipeline/multi-agent-refs/analysis-template-corporate.md +9 -9
- package/pipeline/multi-agent-refs/analysis-template.md +19 -19
- 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 +8 -8
- package/pipeline/multi-agent-refs/conventions-defaults.md +2 -2
- package/pipeline/multi-agent-refs/cross-cli-contract.md +31 -6
- package/pipeline/multi-agent-refs/features/analysis-jira.md +1 -1
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +4 -4
- package/pipeline/multi-agent-refs/features/code-graph.md +5 -5
- 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 +3 -3
- 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 +41 -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 +2 -2
- 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/url-enrichment.md +1 -1
- package/pipeline/multi-agent-refs/features/verify-by-test.md +4 -4
- 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 +8 -8
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +24 -24
- 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/wiki-capture.md +14 -14
- package/pipeline/preferences-template.json +9 -1
- package/pipeline/rules/figma-pipeline.md +8 -8
- 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 +3 -3
- package/pipeline/schemas/analysis-spec.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/figma-project-config.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 +102 -58
- 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/secret-patterns.json +124 -0
- 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 +2 -2
- package/pipeline/scripts/_smoke-root.sh +1 -1
- package/pipeline/scripts/aggregate-metrics.mjs +1 -1
- package/pipeline/scripts/build-references.mjs +2 -2
- package/pipeline/scripts/bulk-read.sh +10 -1
- package/pipeline/scripts/capture-flush.sh +8 -8
- package/pipeline/scripts/capture-resume.sh +3 -3
- package/pipeline/scripts/classify-plan-safety.mjs +1 -1
- package/pipeline/scripts/cost-table.json +8 -1
- package/pipeline/scripts/diff-explain.mjs +1 -1
- package/pipeline/scripts/doctor.mjs +3 -3
- package/pipeline/scripts/gc-abandoned.sh +3 -3
- package/pipeline/scripts/gc-tmp.sh +1 -1
- package/pipeline/scripts/gc-worktrees.sh +1 -1
- package/pipeline/scripts/gen-facts.mjs +280 -0
- package/pipeline/scripts/gen-mode-dispatch.mjs +32 -37
- package/pipeline/scripts/gen-ref-toc.mjs +1 -1
- package/pipeline/scripts/graph-report.mjs +1 -1
- package/pipeline/scripts/jira-attach.sh +1 -1
- package/pipeline/scripts/learn-from-transcripts.mjs +1 -1
- package/pipeline/scripts/learning-curve.mjs +2 -2
- package/pipeline/scripts/log-metric.sh +17 -4
- package/pipeline/scripts/memory-save.sh +1 -1
- package/pipeline/scripts/migrate-prefs.mjs +22 -5
- package/pipeline/scripts/phase-banner.sh +20 -20
- package/pipeline/scripts/phase-tracker.sh +12 -12
- package/pipeline/scripts/plan-coverage-gate.mjs +2 -2
- package/pipeline/scripts/pre-commit-check.sh +30 -1
- package/pipeline/scripts/render-agent-log-cost.sh +1 -1
- package/pipeline/scripts/render-work-summary.sh +3 -3
- package/pipeline/scripts/review-file-filter.mjs +1 -1
- package/pipeline/scripts/run-aggregator.mjs +13 -6
- package/pipeline/scripts/run-metrics.mjs +1 -1
- package/pipeline/scripts/runs-index.mjs +11 -1
- package/pipeline/scripts/scan-skills.sh +26 -0
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +6 -6
- package/pipeline/scripts/smoke-schema-validation.sh +26 -7
- package/pipeline/scripts/token-budget-report.mjs +13 -2
- package/pipeline/scripts/triage-memory.mjs +2 -2
- package/pipeline/scripts/validate-analysis-doc.mjs +274 -43
- 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-triage.mjs +3 -3
- package/pipeline/scripts/verify-citations.mjs +1 -1
- package/pipeline/scripts/worktree-finalize.sh +5 -5
- package/pipeline/scripts/write-state.mjs +32 -0
- package/pipeline/skills/.skill-manifest.json +38 -22
- package/pipeline/skills/.skills-index.json +49 -5
- package/pipeline/skills/shared/README.md +10 -6
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +8 -8
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +81 -82
- 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 +5 -5
- 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/NOTICE-swift-ios-skills.md +1 -1
- package/pipeline/skills/shared/external/signal-community/SKILL.md +8 -1
- package/pipeline/skills/skills-index.md +8 -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
|
@@ -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.
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
// - Manual scenarios (Section 15.7): every MT- row carries a BR- id.
|
|
28
28
|
// - Variant coverage (Section 6.X): every axis row states the full axis and
|
|
29
29
|
// the subset this screen uses; half a row cannot be audited.
|
|
30
|
-
// - Traceability (Full mode only, Locked
|
|
30
|
+
// - Traceability (Full mode only, Locked 30): every BR-<slug>-NN id defined in
|
|
31
31
|
// the Business Rules area is referenced again later (test / acceptance /
|
|
32
32
|
// analytics). A define-only rule is a WARN (heuristic).
|
|
33
33
|
// - No whole-section "TBD" / "Not applicable" placeholder bodies (Locked 2);
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
import { readFileSync } from "node:fs";
|
|
44
44
|
import { runMain } from "../lib/fatal.mjs";
|
|
45
45
|
|
|
46
|
-
// A repo-less run (Locked
|
|
46
|
+
// A repo-less run (Locked 34) still splits by channel, derived from the evidence:
|
|
47
47
|
// "mobile" and "web" are its platform values. "none" is the narrower case where the
|
|
48
48
|
// evidence describes no interface at all. All three are real values, not missing ones.
|
|
49
49
|
// "web" is the pre-"web" spelling, still accepted so older documents validate.
|
|
@@ -63,7 +63,7 @@ const REQUIRED_SECTIONS = [
|
|
|
63
63
|
{ key: "references", any: ["References", "Referanslar"] },
|
|
64
64
|
];
|
|
65
65
|
|
|
66
|
-
// Corporate profile backbone (Locked
|
|
66
|
+
// Corporate profile backbone (Locked 32). These render even with zero evidence,
|
|
67
67
|
// carrying N/A or EKLENECEK, so their absence is a structural failure rather
|
|
68
68
|
// than a legitimate omission.
|
|
69
69
|
const CORPORATE_SECTIONS = [
|
|
@@ -245,16 +245,31 @@ function main() {
|
|
|
245
245
|
for (const k of REQUIRED_FM) {
|
|
246
246
|
if (!parsed.fm[k]) errors.push(`front-matter missing required key: ${k}`);
|
|
247
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.
|
|
248
255
|
const mode = parsed.fm.mode;
|
|
249
|
-
if (mode
|
|
250
|
-
|
|
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}`);
|
|
251
262
|
}
|
|
252
263
|
const plat = parsed.fm.platform;
|
|
253
264
|
if (plat && !KNOWN_PLATFORMS.has(plat)) {
|
|
254
265
|
errors.push(`front-matter platform not in known set: ${plat}`);
|
|
255
266
|
}
|
|
256
267
|
}
|
|
257
|
-
|
|
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;
|
|
258
273
|
const profile = (parsed?.fm?.profile || "global").toLowerCase();
|
|
259
274
|
if (profile !== "global" && profile !== "corporate") {
|
|
260
275
|
errors.push(`front-matter profile must be global|corporate, got: ${profile}`);
|
|
@@ -275,7 +290,7 @@ function main() {
|
|
|
275
290
|
// The development layer is legitimately absent when no repo was selected,
|
|
276
291
|
// so requiring it there would fail every stack-optional document.
|
|
277
292
|
.filter((sec) => !(noPlatform && REPO_DEPENDENT_KEYS.has(sec.key)));
|
|
278
|
-
const rule = profile === "corporate" ? "Locked
|
|
293
|
+
const rule = profile === "corporate" ? "Locked 32" : "Locked 2";
|
|
279
294
|
for (const sec of requiredSections) {
|
|
280
295
|
if (!sec.any.some((kw) => headingBlob.includes(kw))) {
|
|
281
296
|
errors.push(`missing required section (${rule}): ${sec.key}`);
|
|
@@ -301,7 +316,7 @@ function main() {
|
|
|
301
316
|
//
|
|
302
317
|
// It ran only in the corporate profile until now, which made the global one the
|
|
303
318
|
// profile where an inline marker could point at nothing. Two documents already
|
|
304
|
-
// told it to: Locked
|
|
319
|
+
// told it to: Locked 35 says an unstamped gap fails the dispatch gate with no
|
|
305
320
|
// profile clause, and analysis/render.md routes an external-dependency gap to
|
|
306
321
|
// `AS-NN` in Section 20 for every run. The check was the only side that
|
|
307
322
|
// disagreed. Nothing changes for a document that carries no markers: every loop
|
|
@@ -326,13 +341,13 @@ function main() {
|
|
|
326
341
|
// A gap that names no id cannot be traced to an owner, which is the whole
|
|
327
342
|
// point of admitting it.
|
|
328
343
|
if (!/\bAS-\d{2,3}\b/.test(lines[i])) {
|
|
329
|
-
errors.push(`line ${i + 1}: EKLENECEK carries no AS-NN reference (Locked
|
|
344
|
+
errors.push(`line ${i + 1}: EKLENECEK carries no AS-NN reference (Locked 32)`);
|
|
330
345
|
}
|
|
331
346
|
}
|
|
332
347
|
for (const id of referencedIds) {
|
|
333
348
|
if (!definedIds.has(id)) {
|
|
334
349
|
errors.push(
|
|
335
|
-
`${id} is referenced in the body but not defined in Risks and Open Questions (Locked
|
|
350
|
+
`${id} is referenced in the body but not defined in Risks and Open Questions (Locked 32)`,
|
|
336
351
|
);
|
|
337
352
|
}
|
|
338
353
|
}
|
|
@@ -343,7 +358,7 @@ function main() {
|
|
|
343
358
|
const row = risksBlock.split("\n").find((l) => l.includes(id)) || "";
|
|
344
359
|
if (!/(no body reference|govde referansi yok)/i.test(row)) {
|
|
345
360
|
errors.push(
|
|
346
|
-
`${id} is defined in Risks and Open Questions but never referenced in the body (Locked
|
|
361
|
+
`${id} is defined in Risks and Open Questions but never referenced in the body (Locked 32)`,
|
|
347
362
|
);
|
|
348
363
|
}
|
|
349
364
|
}
|
|
@@ -379,13 +394,13 @@ function main() {
|
|
|
379
394
|
const owner = cells[oi] || "";
|
|
380
395
|
if (!owner || /^(-|\?|TBD|EKLENECEK|N\/A)$/i.test(owner)) {
|
|
381
396
|
const id = (row.match(/\bAS-\d{2,3}\b/) || ["a row"])[0];
|
|
382
|
-
errors.push(`${id} names no owner in Risks and Open Questions (Locked
|
|
397
|
+
errors.push(`${id} names no owner in Risks and Open Questions (Locked 35)`);
|
|
383
398
|
}
|
|
384
399
|
}
|
|
385
400
|
} else if (rows.some((l) => /\bAS-\d{2,3}\b/.test(l))) {
|
|
386
401
|
// Only when there is something to own. A Section 20 with no stamped row
|
|
387
402
|
// has no owner to check, and warning there would fire on every document
|
|
388
|
-
// that never needed the column - noise that Phase
|
|
403
|
+
// that never needed the column - noise that Phase 3 runs under --strict.
|
|
389
404
|
warns.push(
|
|
390
405
|
"Risks and Open Questions carries AS-NN rows but no owner column; ownership cannot be checked",
|
|
391
406
|
);
|
|
@@ -424,7 +439,7 @@ function main() {
|
|
|
424
439
|
}
|
|
425
440
|
}
|
|
426
441
|
for (const id of [...openMarkers].sort()) {
|
|
427
|
-
errors.push(`status: final but ${id} is still open (Locked
|
|
442
|
+
errors.push(`status: final but ${id} is still open (Locked 35 closure gate)`);
|
|
428
443
|
}
|
|
429
444
|
const stillOpen =
|
|
430
445
|
risksStatusIdx >= 0
|
|
@@ -474,7 +489,7 @@ function main() {
|
|
|
474
489
|
if (mode === "full") {
|
|
475
490
|
const matrixStart = lines.findIndex((l) => /^#{2,4}\s/.test(l) && MATRIX_HEADING.test(l));
|
|
476
491
|
if (matrixStart === -1) {
|
|
477
|
-
errors.push("corporate Full mode requires the requirement traceability matrix (Locked
|
|
492
|
+
errors.push("corporate Full mode requires the requirement traceability matrix (Locked 32)");
|
|
478
493
|
} else {
|
|
479
494
|
let matrixBlock = "";
|
|
480
495
|
for (let i = matrixStart + 1; i < lines.length; i++) {
|
|
@@ -505,7 +520,7 @@ function main() {
|
|
|
505
520
|
}
|
|
506
521
|
mark("traceability-matrix");
|
|
507
522
|
} else {
|
|
508
|
-
mark("traceability-matrix"
|
|
523
|
+
mark("traceability-matrix");
|
|
509
524
|
}
|
|
510
525
|
} else {
|
|
511
526
|
mark("payload-in-table-cell", "global profile");
|
|
@@ -562,7 +577,7 @@ function main() {
|
|
|
562
577
|
headingBlobLower.includes("ui-test");
|
|
563
578
|
if (!hasUi) {
|
|
564
579
|
errors.push(
|
|
565
|
-
"front-matter ui_tests: true but no Section 15.6 UI test scenarios found (Locked
|
|
580
|
+
"front-matter ui_tests: true but no Section 15.6 UI test scenarios found (Locked 30)",
|
|
566
581
|
);
|
|
567
582
|
}
|
|
568
583
|
}
|
|
@@ -580,12 +595,12 @@ function main() {
|
|
|
580
595
|
|
|
581
596
|
mark("optin-coverage-sections");
|
|
582
597
|
|
|
583
|
-
// 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
|
|
584
599
|
// written first come from it. Full mode without it hands dev an empty matrix.
|
|
585
600
|
// It is Section 15 in the global profile and Section 19 in the corporate one;
|
|
586
601
|
// the check matches on the title, so only the stack-optional case needs an
|
|
587
602
|
// exemption - there the whole development layer is legitimately absent
|
|
588
|
-
// (Locked
|
|
603
|
+
// (Locked 34) and there is no repo for a test to run against yet.
|
|
589
604
|
if (mode === "full" && !noPlatform) {
|
|
590
605
|
const hasTestPlan = ["Test Planı", "Test Plani", "Test Plan"].some((kw) =>
|
|
591
606
|
headingBlob.includes(kw),
|
|
@@ -594,12 +609,12 @@ function main() {
|
|
|
594
609
|
// The omission table allows exactly one case: a backend-only run with no
|
|
595
610
|
// contract testing planned. The validator knows the platform, so that case
|
|
596
611
|
// warns instead of blocking; every other platform hands dev an empty matrix.
|
|
597
|
-
const msg = "no Section 15 Test Plan;
|
|
612
|
+
const msg = "no Section 15 Test Plan; dev reads it as the RED input (Locked 30)";
|
|
598
613
|
if (parsed?.fm?.platform === "backend") {
|
|
599
614
|
warns.push(`${msg} - allowed for backend only when no contract testing is planned`);
|
|
600
615
|
} else {
|
|
601
616
|
errors.push(
|
|
602
|
-
`missing Section 15 Test Plan; Full mode dev reads it as the RED input (Locked
|
|
617
|
+
`missing Section 15 Test Plan; Full mode dev reads it as the RED input (Locked 30)`,
|
|
603
618
|
);
|
|
604
619
|
}
|
|
605
620
|
} else {
|
|
@@ -609,14 +624,13 @@ function main() {
|
|
|
609
624
|
headingBlobLower.includes("unit test");
|
|
610
625
|
if (!hasUnit && /BR-[a-z0-9]+(?:-[a-z0-9]+)*-\d+/i.test(text)) {
|
|
611
626
|
errors.push(
|
|
612
|
-
"Section 15 has no unit-test sub-table while business rules are defined (Locked
|
|
627
|
+
"Section 15 has no unit-test sub-table while business rules are defined (Locked 30)",
|
|
613
628
|
);
|
|
614
629
|
}
|
|
615
630
|
}
|
|
616
631
|
}
|
|
617
632
|
|
|
618
|
-
if (
|
|
619
|
-
else if (noPlatform) mark("test-plan", "no target platform");
|
|
633
|
+
if (noPlatform) mark("test-plan", "no target platform");
|
|
620
634
|
else mark("test-plan");
|
|
621
635
|
|
|
622
636
|
// 2d. Section 3 carries the diagrams. Omission is legitimate under the
|
|
@@ -726,7 +740,7 @@ function main() {
|
|
|
726
740
|
|
|
727
741
|
mark("concept-table-evidence", conceptBody ? undefined : "no concept table");
|
|
728
742
|
|
|
729
|
-
// 2g. Locked
|
|
743
|
+
// 2g. Locked 30 main clause: a rule with no test scenario fails the gate.
|
|
730
744
|
// The pre-existing occurrence-count heuristic below stays as the looser net.
|
|
731
745
|
if (mode === "full") {
|
|
732
746
|
const idRx = /BR-[a-z0-9]+(?:-[a-z0-9]+)*-\d+/gi;
|
|
@@ -741,15 +755,15 @@ function main() {
|
|
|
741
755
|
const tested = new Set((testBody.join("\n").match(idRx) || []).map((x) => x.toUpperCase()));
|
|
742
756
|
for (const id of defined) {
|
|
743
757
|
if (!tested.has(id)) {
|
|
744
|
-
errors.push(`business rule ${id} has no Section 15 test scenario (Locked
|
|
758
|
+
errors.push(`business rule ${id} has no Section 15 test scenario (Locked 30)`);
|
|
745
759
|
}
|
|
746
760
|
}
|
|
747
761
|
}
|
|
748
762
|
}
|
|
749
763
|
|
|
750
|
-
mark("rule-to-test"
|
|
764
|
+
mark("rule-to-test");
|
|
751
765
|
|
|
752
|
-
// 2h. Redesign mode (Locked
|
|
766
|
+
// 2h. Redesign mode (Locked 36). Eight checks, live only when the front-matter
|
|
753
767
|
// opts in. The contract they enforce is analysis/redesign.md, which loads only
|
|
754
768
|
// on a redesign run; what is here is the enforcement, not the explanation.
|
|
755
769
|
const redesign = String(parsed?.fm?.redesign || "false").toLowerCase() === "true";
|
|
@@ -773,7 +787,7 @@ function main() {
|
|
|
773
787
|
[epBody, "4.6 Endpoint Mapping"],
|
|
774
788
|
[dlBody, "9.5 Difference List"],
|
|
775
789
|
]) {
|
|
776
|
-
if (!body) errors.push(`redesign: true but Section ${name} is missing (Locked
|
|
790
|
+
if (!body) errors.push(`redesign: true but Section ${name} is missing (Locked 36)`);
|
|
777
791
|
}
|
|
778
792
|
|
|
779
793
|
const STATUSES = ["Moved", "Partial", "Missing", "New", "Out of scope"];
|
|
@@ -790,11 +804,11 @@ function main() {
|
|
|
790
804
|
// the row that was not marked.
|
|
791
805
|
if (!/:\d+\b/.test(evidence) && !certainty.includes("uncertain")) {
|
|
792
806
|
errors.push(
|
|
793
|
-
`current-behaviour row ${id} cites no file:line and is not marked uncertain (Locked
|
|
807
|
+
`current-behaviour row ${id} cites no file:line and is not marked uncertain (Locked 36)`,
|
|
794
808
|
);
|
|
795
809
|
}
|
|
796
810
|
// 3. A rule that lives only in a shared module legitimately reads this way,
|
|
797
|
-
// so it warns rather than blocks - and Phase
|
|
811
|
+
// so it warns rather than blocks - and Phase 3 runs --strict, so it blocks
|
|
798
812
|
// there, which is where a human is already looking.
|
|
799
813
|
if (/cross-cutting/i.test(evidence) && certainty.includes("confirmed")) {
|
|
800
814
|
warns.push(
|
|
@@ -811,14 +825,14 @@ function main() {
|
|
|
811
825
|
// 4. A controlled vocabulary decaying into free text.
|
|
812
826
|
if (!status) {
|
|
813
827
|
errors.push(
|
|
814
|
-
`difference-list row ${id} has status "${statusCell}"; expected one of ${STATUSES.join(" / ")} (Locked
|
|
828
|
+
`difference-list row ${id} has status "${statusCell}"; expected one of ${STATUSES.join(" / ")} (Locked 36)`,
|
|
815
829
|
);
|
|
816
830
|
continue;
|
|
817
831
|
}
|
|
818
832
|
// 5. The reason the mode exists: a behaviour v2 drops with no decision.
|
|
819
833
|
if ((status === "Missing" || status === "Partial") && !/\bAS-\d{2,3}\b/.test(row)) {
|
|
820
834
|
errors.push(
|
|
821
|
-
`difference-list row ${id} is ${status} with no AS-NN open question (Locked
|
|
835
|
+
`difference-list row ${id} is ${status} with no AS-NN open question (Locked 36)`,
|
|
822
836
|
);
|
|
823
837
|
}
|
|
824
838
|
}
|
|
@@ -852,7 +866,7 @@ function main() {
|
|
|
852
866
|
const cellCount = (line) =>
|
|
853
867
|
line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").length;
|
|
854
868
|
if (!header) {
|
|
855
|
-
errors.push("Section 4.6 has no v1/v2 header row (Locked
|
|
869
|
+
errors.push("Section 4.6 has no v1/v2 header row (Locked 36)");
|
|
856
870
|
} else {
|
|
857
871
|
const cols = cellCount(header);
|
|
858
872
|
for (const row of tableDataRows(epBody)) {
|
|
@@ -871,7 +885,7 @@ function main() {
|
|
|
871
885
|
// keyword as a raw substring of the heading line - deliberately, since Locked
|
|
872
886
|
// 2 re-flows section numbers and the corporate profile numbers them
|
|
873
887
|
// differently - so an unrelated `### 4.9 Notes on the old Current Behaviour
|
|
874
|
-
// audit process` matched, and Phase
|
|
888
|
+
// audit process` matched, and Phase 3 runs --strict, which turns this warning
|
|
875
889
|
// into a blocked document. The artefact itself is the honest signal: a
|
|
876
890
|
// rendered current-behaviour table always carries `CB-<slug>-NN` ids, and a
|
|
877
891
|
// paragraph that merely uses the words never does.
|
|
@@ -911,7 +925,7 @@ function main() {
|
|
|
911
925
|
|
|
912
926
|
// 4. Placeholder whole-section bodies (Locked 2).
|
|
913
927
|
// Not a defect in the corporate profile: there N/A and EKLENECEK are how a
|
|
914
|
-
// backbone section reports "considered, nothing to state" (Locked
|
|
928
|
+
// backbone section reports "considered, nothing to state" (Locked 32), and the
|
|
915
929
|
// EKLENECEK-to-open-question pairing above is what keeps them honest.
|
|
916
930
|
if (profile === "global" && /^\s*(TBD|Not applicable|N\/A)\s*$/im.test(text)) {
|
|
917
931
|
warns.push(
|
|
@@ -921,7 +935,49 @@ function main() {
|
|
|
921
935
|
|
|
922
936
|
mark("placeholder-bodies", profile === "global" ? undefined : "corporate profile");
|
|
923
937
|
|
|
924
|
-
//
|
|
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)
|
|
925
981
|
if (mode === "full") {
|
|
926
982
|
const idRe = /BR-[a-z0-9]+(?:-[a-z0-9]+)*-\d+/gi;
|
|
927
983
|
const all = text.match(idRe) || [];
|
|
@@ -931,18 +987,18 @@ function main() {
|
|
|
931
987
|
counts.set(key, (counts.get(key) || 0) + 1);
|
|
932
988
|
}
|
|
933
989
|
if (counts.size === 0 && profile === "global") {
|
|
934
|
-
warns.push("
|
|
990
|
+
warns.push("no BR-<slug>-NN business-rule ids found (Section 4.4 / Locked 30)");
|
|
935
991
|
}
|
|
936
992
|
for (const [id, n] of counts) {
|
|
937
993
|
if (n < 2) {
|
|
938
994
|
warns.push(
|
|
939
|
-
`business rule ${id} is defined but never referenced downstream (test/acceptance/analytics) - Locked
|
|
995
|
+
`business rule ${id} is defined but never referenced downstream (test/acceptance/analytics) - Locked 30 traceability`,
|
|
940
996
|
);
|
|
941
997
|
}
|
|
942
998
|
}
|
|
943
999
|
}
|
|
944
1000
|
|
|
945
|
-
mark("br-reference-count"
|
|
1001
|
+
mark("br-reference-count");
|
|
946
1002
|
|
|
947
1003
|
// 6. Manual test scenarios (Section 15.7): every MT row carries a BR id.
|
|
948
1004
|
// A manual scenario nobody can trace back to a rule is a scenario nobody can
|
|
@@ -951,7 +1007,7 @@ function main() {
|
|
|
951
1007
|
if (!/^\|\s*MT-[a-z0-9-]+/i.test(line)) continue;
|
|
952
1008
|
if (!/BR-[a-z0-9]+(?:-[a-z0-9]+)*-\d+/i.test(line)) {
|
|
953
1009
|
const id = (line.match(/MT-[a-z0-9-]+/i) || ["MT row"])[0];
|
|
954
|
-
errors.push(`manual scenario ${id} has no BR- id (Section 15.7 / Locked
|
|
1010
|
+
errors.push(`manual scenario ${id} has no BR- id (Section 15.7 / Locked 30)`);
|
|
955
1011
|
}
|
|
956
1012
|
}
|
|
957
1013
|
|
|
@@ -978,13 +1034,188 @@ function main() {
|
|
|
978
1034
|
if (/^-+$/.test(cells[1]) || /Bile\u015fen|Component/i.test(cells[1])) continue;
|
|
979
1035
|
if (!cells[3] || !cells[4]) {
|
|
980
1036
|
errors.push(
|
|
981
|
-
`variant row "${cells[1]}" is missing the all-values or used-here column (Section 6.X / Locked
|
|
1037
|
+
`variant row "${cells[1]}" is missing the all-values or used-here column (Section 6.X / Locked 28)`,
|
|
982
1038
|
);
|
|
983
1039
|
}
|
|
984
1040
|
}
|
|
985
1041
|
|
|
986
1042
|
mark("variant-coverage");
|
|
987
1043
|
|
|
1044
|
+
// 2h. Locked 13, 14, 15, 17, 18 and 21.
|
|
1045
|
+
//
|
|
1046
|
+
// Each checks a SHAPE the template prescribes. None judges content: a gate
|
|
1047
|
+
// that guesses at meaning fails honest documents, and a gate people learn to
|
|
1048
|
+
// ignore is worse than the prose it replaced.
|
|
1049
|
+
//
|
|
1050
|
+
// Each is keyed off a structure only a real document carries, so a minimal
|
|
1051
|
+
// document is skipped rather than failed. "Section 4 exists" is not evidence
|
|
1052
|
+
// that anybody wrote scenarios, and "Section 4 has a 4.1 sub-heading" is.
|
|
1053
|
+
|
|
1054
|
+
// Locked 13: Section 4 scenarios are Gherkin, not prose.
|
|
1055
|
+
const storiesBody = sectionBody(allLines, [
|
|
1056
|
+
"Kullanıcı Hikayeleri",
|
|
1057
|
+
"Kullanici Hikayeleri",
|
|
1058
|
+
"User Stories",
|
|
1059
|
+
]);
|
|
1060
|
+
if (storiesBody) {
|
|
1061
|
+
const blocks = [];
|
|
1062
|
+
let inFence = false;
|
|
1063
|
+
let isGherkin = false;
|
|
1064
|
+
let buf = [];
|
|
1065
|
+
for (const line of storiesBody) {
|
|
1066
|
+
if (line.trim().startsWith("```")) {
|
|
1067
|
+
if (!inFence) {
|
|
1068
|
+
inFence = true;
|
|
1069
|
+
isGherkin = /^```\s*gherkin/i.test(line.trim());
|
|
1070
|
+
buf = [];
|
|
1071
|
+
} else {
|
|
1072
|
+
if (isGherkin) blocks.push(buf.join("\n"));
|
|
1073
|
+
inFence = false;
|
|
1074
|
+
}
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
if (inFence && isGherkin) buf.push(line);
|
|
1078
|
+
}
|
|
1079
|
+
// Scenarios are 4.1 to 4.3. 4.4 is the business-rule table, and a redesign
|
|
1080
|
+
// run adds 4.5 and up (Locked 36: current behaviour, endpoint mapping,
|
|
1081
|
+
// difference list) - none of which is a scenario, so "everything except
|
|
1082
|
+
// 4.4" would demand Gherkin of three tables.
|
|
1083
|
+
const scenarioHeads = storiesBody.filter((l) => /^#{3}\s+4\.[1-3]\b/.test(l));
|
|
1084
|
+
if (scenarioHeads.length && !blocks.length) {
|
|
1085
|
+
errors.push(
|
|
1086
|
+
`Section 4 has ${scenarioHeads.length} scenario sub-section(s) and no \`\`\`gherkin block; plain-prose user stories are rejected (Locked 13)`,
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
blocks.forEach((b, i) => {
|
|
1090
|
+
const missing = ["Given", "When", "Then"].filter(
|
|
1091
|
+
(k) => !new RegExp(`(^|\\n)\\s*${k}\\b`).test(b),
|
|
1092
|
+
);
|
|
1093
|
+
if (missing.length) {
|
|
1094
|
+
errors.push(`Section 4 gherkin block ${i + 1} states no ${missing.join(", ")} (Locked 13)`);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
mark("gherkin-scenarios", storiesBody ? undefined : "no User Stories section");
|
|
1100
|
+
|
|
1101
|
+
// Locked 14: Goals and Non-Goals are paired. A goal with an empty counterpart
|
|
1102
|
+
// cell is the failure the decision names - omission cannot be read as
|
|
1103
|
+
// exclusion, so the column has to say what is NOT covered.
|
|
1104
|
+
const goalsBody = sectionBody(allLines, [
|
|
1105
|
+
"Hedefler ve Karşı Hedefler",
|
|
1106
|
+
"Hedefler ve Karsi Hedefler",
|
|
1107
|
+
"Goals and Non-Goals",
|
|
1108
|
+
"Goals",
|
|
1109
|
+
]);
|
|
1110
|
+
let goalRows = 0;
|
|
1111
|
+
if (goalsBody) {
|
|
1112
|
+
for (const row of tableDataRows(goalsBody)) {
|
|
1113
|
+
const cells = row
|
|
1114
|
+
.split("|")
|
|
1115
|
+
.map((c) => c.trim())
|
|
1116
|
+
.filter((c, i, a) => i !== 0 && i !== a.length - 1);
|
|
1117
|
+
if (cells.length < 2) continue;
|
|
1118
|
+
goalRows++;
|
|
1119
|
+
if (!cells[1] || cells[1] === "-") {
|
|
1120
|
+
errors.push(`goal "${cells[0].slice(0, 40)}" has no paired non-goal (Locked 14)`);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
mark("goals-paired", goalRows ? undefined : "no Goals/Non-Goals table");
|
|
1126
|
+
|
|
1127
|
+
// Locked 15: a new asset that is not SVG owes a rationale. The decision
|
|
1128
|
+
// allows Lottie and optimized PNG - it does not allow them silently.
|
|
1129
|
+
const assetsBody = sectionBody(allLines, ["Asset Envanteri", "Asset Inventory"]);
|
|
1130
|
+
let assetRows = 0;
|
|
1131
|
+
if (assetsBody) {
|
|
1132
|
+
for (const row of tableDataRows(assetsBody)) {
|
|
1133
|
+
const cells = row
|
|
1134
|
+
.split("|")
|
|
1135
|
+
.map((c) => c.trim())
|
|
1136
|
+
.filter((c, i, a) => i !== 0 && i !== a.length - 1);
|
|
1137
|
+
if (cells.length < 5) continue;
|
|
1138
|
+
assetRows++;
|
|
1139
|
+
const [name, format, existing, , notes] = cells;
|
|
1140
|
+
const isNew = /^(no|hayır|hayir)\b/i.test(existing);
|
|
1141
|
+
if (!isNew) continue;
|
|
1142
|
+
if (/\bsvg\b/i.test(format)) continue;
|
|
1143
|
+
if (!notes || !/\S/.test(notes.replace(/^new,?\s*/i, ""))) {
|
|
1144
|
+
errors.push(
|
|
1145
|
+
`new asset "${name.slice(0, 40)}" is ${format} rather than SVG and states no exception rationale (Locked 15)`,
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
mark("asset-format", assetRows ? undefined : "no Asset Inventory table");
|
|
1152
|
+
|
|
1153
|
+
// Locked 17: "other errors" is the phrase the decision exists to reject. An
|
|
1154
|
+
// exhaustive list cannot be checked for exhaustiveness from inside the
|
|
1155
|
+
// document, but the admission that it is not exhaustive can.
|
|
1156
|
+
const apiBody = sectionBody(allLines, ["API Kontratları", "API Kontratlari", "API Contracts"]);
|
|
1157
|
+
if (apiBody) {
|
|
1158
|
+
const VAGUE = [
|
|
1159
|
+
/\bother errors?\b/i,
|
|
1160
|
+
/\bvarious \dxx\b/i,
|
|
1161
|
+
/\bdiğer hatalar\b/i,
|
|
1162
|
+
/\bdiger hatalar\b/i,
|
|
1163
|
+
/\betc\.?\s*\|/i,
|
|
1164
|
+
];
|
|
1165
|
+
for (const line of apiBody) {
|
|
1166
|
+
const hit = VAGUE.find((rx) => rx.test(line));
|
|
1167
|
+
if (hit) {
|
|
1168
|
+
errors.push(
|
|
1169
|
+
`Section 9 stands in for a status code instead of listing it: "${line.trim().slice(0, 60)}" (Locked 17)`,
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
mark("api-variants-exhaustive", apiBody ? undefined : "no API Contracts section");
|
|
1176
|
+
|
|
1177
|
+
// Locked 18: screenshots are embedded, not linked. A remote image survives
|
|
1178
|
+
// exactly as long as whatever serves it, and the document outlives that.
|
|
1179
|
+
const designBody = sectionBody(allLines, [
|
|
1180
|
+
"Tasarım Referansı",
|
|
1181
|
+
"Tasarim Referansi",
|
|
1182
|
+
"Design Reference",
|
|
1183
|
+
]);
|
|
1184
|
+
if (designBody) {
|
|
1185
|
+
for (const line of designBody) {
|
|
1186
|
+
const m = /!\[[^\]]*\]\((https?:\/\/[^)]+)\)/.exec(line);
|
|
1187
|
+
if (m) {
|
|
1188
|
+
errors.push(
|
|
1189
|
+
`Section 5 links a remote screenshot instead of embedding a local file: ${m[1].slice(0, 60)} (Locked 18)`,
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
mark("screenshots-embedded", designBody ? undefined : "no Design Reference section");
|
|
1196
|
+
|
|
1197
|
+
// Locked 21: References sit at the bottom. The reader sees goals, flow and
|
|
1198
|
+
// design before sources, which is only true if no numbered section follows.
|
|
1199
|
+
const topNums = [];
|
|
1200
|
+
let refNum = null;
|
|
1201
|
+
for (const line of allLines) {
|
|
1202
|
+
const m = /^##\s+(\d+)\.\s+(.*)$/.exec(line);
|
|
1203
|
+
if (!m) continue;
|
|
1204
|
+
const n = Number(m[1]);
|
|
1205
|
+
topNums.push(n);
|
|
1206
|
+
if (/Referanslar|References/i.test(m[2])) refNum = n;
|
|
1207
|
+
}
|
|
1208
|
+
if (refNum !== null && topNums.length) {
|
|
1209
|
+
const highest = Math.max(...topNums);
|
|
1210
|
+
if (refNum !== highest) {
|
|
1211
|
+
errors.push(
|
|
1212
|
+
`References is Section ${refNum} but Section ${highest} follows it, and sources go last (Locked 21)`,
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
mark("references-last", refNum === null ? "no References section" : undefined);
|
|
1218
|
+
|
|
988
1219
|
// Report
|
|
989
1220
|
if (report) {
|
|
990
1221
|
const width = Math.max(...checkRows.map((r) => r.id.length));
|
|
@@ -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
|
}
|