mandrel 2.30.0 → 2.32.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/.agents/README.md +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +4 -4
- package/.agents/docs/agentrc-reference.json +61 -57
- package/.agents/docs/configuration.md +274 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/ci-remediation.md +44 -1
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +0 -1
- package/.agents/schemas/agentrc.schema.json +1939 -1400
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +7 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +136 -0
- package/.agents/scripts/check-lifecycle-lint.js +36 -112
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-entry-sync.js +469 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +17 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +65 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/story-body.js +12 -59
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +0 -2
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +2 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +2 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +49 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +12 -3
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- package/.agents/workflows/helpers/signals.md +0 -112
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* node:coverage ignore file -- top-level docs-freshness gate shell; the tested logic lives in lib/docs-freshness.js */
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* .agents/scripts/validate-docs-freshness.js — Documentation Freshness Gate
|
|
6
|
-
*
|
|
7
|
-
* For each doc in `delivery.docsFreshness.paths` + `project.docsContextFiles`, verify
|
|
8
|
-
* that the file was meaningfully updated during this Epic's lifecycle. A
|
|
9
|
-
* file passes when **either** of the following holds:
|
|
10
|
-
*
|
|
11
|
-
* 1. `git log --all --grep="#<epicId>" -- <file>` returns a commit —
|
|
12
|
-
* the Epic ID was referenced in a commit message that touched the
|
|
13
|
-
* file. This is the pass path for **every** doc.
|
|
14
|
-
* 2. The file's current body contains `#<epicId>` — but this
|
|
15
|
-
* body-annotation path is accepted **only for changelog-class files**
|
|
16
|
-
* (basename matches `/changelog/i`), where an appended release note
|
|
17
|
-
* keyed to the Epic is the legitimate, expected update. Any other doc
|
|
18
|
-
* (architecture, decisions, README, …) MUST pass via condition 1: the
|
|
19
|
-
* living doc has to be **rewritten in an Epic-referencing commit**, not
|
|
20
|
-
* merely annotated with `#<epicId>`.
|
|
21
|
-
*
|
|
22
|
-
* The prior gate accepted any diff against the base branch — a stray
|
|
23
|
-
* whitespace edit or a one-line unrelated cleanup passed, defeating the
|
|
24
|
-
* purpose of the check. Requiring an Epic-ID reference makes "did you
|
|
25
|
-
* update the docs for this Epic?" a falsifiable question instead of a
|
|
26
|
-
* checkbox. The changelog-only restriction on condition 2 closes the
|
|
27
|
-
* follow-on perverse incentive: without it, the gate rewarded appending
|
|
28
|
-
* `#<epicId>` history into living docs (manufacturing fake provenance) to
|
|
29
|
-
* satisfy the check. Restricting the annotation path to changelog files
|
|
30
|
-
* makes the gate ask "was this doc rewritten for the Epic?" rather than
|
|
31
|
-
* "does it mention the Epic?".
|
|
32
|
-
*
|
|
33
|
-
* Usage:
|
|
34
|
-
* node .agents/scripts/validate-docs-freshness.js --epic <EPIC_ID> [--docs <comma-separated>] [--json]
|
|
35
|
-
*
|
|
36
|
-
* `--json` emits a single JSON object on stdout with
|
|
37
|
-
* { ok, epicId, results: [{ file, pass, reason }, ...] }
|
|
38
|
-
* and suppresses the human-readable log lines. Intended for LLM/tool consumers
|
|
39
|
-
* that need to enumerate failing files without parsing log output.
|
|
40
|
-
*
|
|
41
|
-
* Exit codes:
|
|
42
|
-
* 0 — every doc has an Epic-ID reference.
|
|
43
|
-
* 1 — one or more docs have no reference.
|
|
44
|
-
* 2 — configuration error.
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
import fs from 'node:fs';
|
|
48
|
-
import path from 'node:path';
|
|
49
|
-
import { parseArgs } from 'node:util';
|
|
50
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
51
|
-
import {
|
|
52
|
-
getPaths,
|
|
53
|
-
PROJECT_ROOT,
|
|
54
|
-
resolveConfig,
|
|
55
|
-
} from './lib/config-resolver.js';
|
|
56
|
-
import { gitSpawn } from './lib/git-utils.js';
|
|
57
|
-
import { Logger } from './lib/Logger.js';
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Resolve the canonical doc list for a release: `delivery.docsFreshness.paths`
|
|
61
|
-
* entries plus `project.docsContextFiles` prefixed by `project.paths.docsRoot`.
|
|
62
|
-
* Expects the full resolved config (`{ project, delivery, ... }`).
|
|
63
|
-
*
|
|
64
|
-
* @param {object} config
|
|
65
|
-
* @returns {string[]}
|
|
66
|
-
*/
|
|
67
|
-
export function resolveDocList(config) {
|
|
68
|
-
// Read from new shape first; fall back to the legacy shim/bag.
|
|
69
|
-
const project = config?.project ?? config;
|
|
70
|
-
const delivery = config?.delivery ?? null;
|
|
71
|
-
const docsFreshnessPaths = Array.isArray(delivery?.docsFreshness?.paths)
|
|
72
|
-
? delivery.docsFreshness.paths
|
|
73
|
-
: Array.isArray(config?.release?.docs)
|
|
74
|
-
? config.release.docs
|
|
75
|
-
: [];
|
|
76
|
-
const contextDocs = Array.isArray(project?.docsContextFiles)
|
|
77
|
-
? project.docsContextFiles
|
|
78
|
-
: Array.isArray(config?.docsContextFiles)
|
|
79
|
-
? config.docsContextFiles
|
|
80
|
-
: [];
|
|
81
|
-
const docsRoot = getPaths(config).docsRoot ?? 'docs';
|
|
82
|
-
const resolved = [
|
|
83
|
-
...docsFreshnessPaths,
|
|
84
|
-
...contextDocs.map((f) => path.posix.join(docsRoot, f)),
|
|
85
|
-
];
|
|
86
|
-
return Array.from(new Set(resolved));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* A doc is "changelog-class" when its basename matches `/changelog/i`
|
|
91
|
-
* (e.g. `CHANGELOG.md`, `docs/CHANGELOG.md`, `changelog.mdx`). Only these
|
|
92
|
-
* files may satisfy the freshness gate via a body annotation (pass
|
|
93
|
-
* condition 2); every other doc must pass via an Epic-referencing commit
|
|
94
|
-
* (pass condition 1).
|
|
95
|
-
*
|
|
96
|
-
* @param {string} file
|
|
97
|
-
* @returns {boolean}
|
|
98
|
-
*/
|
|
99
|
-
export function isChangelogClass(file) {
|
|
100
|
-
return /changelog/i.test(path.basename(file));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function epicRefMatcher(epicId) {
|
|
104
|
-
// Match `#N` as a standalone token. `(?!\d)` prevents `#10` from
|
|
105
|
-
// satisfying a search for `#1` — a subtle bug the prior diff-only gate
|
|
106
|
-
// never had to guard against.
|
|
107
|
-
return new RegExp(`#${epicId}(?!\\d)`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/* node:coverage disable -- real `git log` shell-out; exercised via the
|
|
111
|
-
injectable `commitsForFile` seam in runFreshnessGate, not directly. */
|
|
112
|
-
function commitsMentioningEpic(docPath, epicId, cwd = PROJECT_ROOT) {
|
|
113
|
-
const res = gitSpawn(
|
|
114
|
-
cwd,
|
|
115
|
-
'log',
|
|
116
|
-
'--all',
|
|
117
|
-
`--grep=#${epicId}`,
|
|
118
|
-
'--pretty=format:%H',
|
|
119
|
-
'--',
|
|
120
|
-
docPath,
|
|
121
|
-
);
|
|
122
|
-
if (res.status !== 0) return [];
|
|
123
|
-
return (res.stdout ?? '')
|
|
124
|
-
.split('\n')
|
|
125
|
-
.map((s) => s.trim())
|
|
126
|
-
.filter(Boolean);
|
|
127
|
-
}
|
|
128
|
-
/* node:coverage enable */
|
|
129
|
-
|
|
130
|
-
function fileBodyMentionsEpic(
|
|
131
|
-
docPath,
|
|
132
|
-
epicId,
|
|
133
|
-
cwd = PROJECT_ROOT,
|
|
134
|
-
readFileImpl = fs.readFileSync,
|
|
135
|
-
) {
|
|
136
|
-
const abs = path.isAbsolute(docPath) ? docPath : path.join(cwd, docPath);
|
|
137
|
-
let body;
|
|
138
|
-
try {
|
|
139
|
-
body = readFileImpl(abs, 'utf8');
|
|
140
|
-
} catch {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
return epicRefMatcher(epicId).test(body);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Run the freshness gate against every resolved doc. Pure; takes
|
|
148
|
-
* everything it needs as inputs so tests don't need a worktree.
|
|
149
|
-
*
|
|
150
|
-
* @param {{
|
|
151
|
-
* epicId: number,
|
|
152
|
-
* docs: string[],
|
|
153
|
-
* cwd?: string,
|
|
154
|
-
* readFileImpl?: typeof fs.readFileSync,
|
|
155
|
-
* commitsForFile?: (doc: string, epicId: number, cwd: string) => string[],
|
|
156
|
-
* }} opts
|
|
157
|
-
* @returns {{ ok: boolean, results: Array<{ file: string, pass: boolean, reason: string }> }}
|
|
158
|
-
*/
|
|
159
|
-
export function runFreshnessGate({
|
|
160
|
-
epicId,
|
|
161
|
-
docs,
|
|
162
|
-
cwd = PROJECT_ROOT,
|
|
163
|
-
readFileImpl = fs.readFileSync,
|
|
164
|
-
commitsForFile = commitsMentioningEpic,
|
|
165
|
-
}) {
|
|
166
|
-
const results = docs.map((file) => {
|
|
167
|
-
const commits = commitsForFile(file, epicId, cwd);
|
|
168
|
-
if (commits.length > 0) {
|
|
169
|
-
return {
|
|
170
|
-
file,
|
|
171
|
-
pass: true,
|
|
172
|
-
reason: `${commits.length} commit(s) reference Epic #${epicId}`,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
// Pass condition 2 (body annotation) is restricted to changelog-class
|
|
176
|
-
// files. For every other doc, an appended `#<epicId>` no longer passes —
|
|
177
|
-
// the living doc must be rewritten in an Epic-referencing commit.
|
|
178
|
-
const changelogClass = isChangelogClass(file);
|
|
179
|
-
if (
|
|
180
|
-
changelogClass &&
|
|
181
|
-
fileBodyMentionsEpic(file, epicId, cwd, readFileImpl)
|
|
182
|
-
) {
|
|
183
|
-
return {
|
|
184
|
-
file,
|
|
185
|
-
pass: true,
|
|
186
|
-
reason: `changelog body annotation references #${epicId}`,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
return {
|
|
190
|
-
file,
|
|
191
|
-
pass: false,
|
|
192
|
-
reason: changelogClass
|
|
193
|
-
? `no commit message or changelog body reference to #${epicId}`
|
|
194
|
-
: `${file} was not rewritten in an Epic-referencing commit for #${epicId} — ` +
|
|
195
|
-
`living docs must be REWRITTEN in a commit whose message references ` +
|
|
196
|
-
`#${epicId} (not annotated with #${epicId}); the body-annotation path ` +
|
|
197
|
-
`passes only for changelog-class files`,
|
|
198
|
-
};
|
|
199
|
-
});
|
|
200
|
-
return { ok: results.every((r) => r.pass), results };
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Pure: parse argv into the normalized CLI option bag.
|
|
205
|
-
*
|
|
206
|
-
* @param {string[]} argv
|
|
207
|
-
* @returns {{ epicId: number|null, json: boolean, docsList: string[]|null }}
|
|
208
|
-
*/
|
|
209
|
-
export function parseFreshnessArgs(argv) {
|
|
210
|
-
const { values } = parseArgs({
|
|
211
|
-
args: argv,
|
|
212
|
-
options: {
|
|
213
|
-
epic: { type: 'string' },
|
|
214
|
-
docs: { type: 'string' },
|
|
215
|
-
json: { type: 'boolean', default: false },
|
|
216
|
-
},
|
|
217
|
-
strict: false,
|
|
218
|
-
});
|
|
219
|
-
const parsed = Number.parseInt(values.epic ?? '', 10);
|
|
220
|
-
return {
|
|
221
|
-
epicId: Number.isNaN(parsed) || parsed <= 0 ? null : parsed,
|
|
222
|
-
json: values.json === true,
|
|
223
|
-
docsList: values.docs
|
|
224
|
-
? values.docs
|
|
225
|
-
.split(',')
|
|
226
|
-
.map((s) => s.trim())
|
|
227
|
-
.filter(Boolean)
|
|
228
|
-
: null,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/** Pure: render a per-doc "ok / fail" line. */
|
|
233
|
-
export function renderFreshnessLine(result) {
|
|
234
|
-
return `[docs-freshness] ${result.pass ? '✅' : '❌'} ${result.file} — ${result.reason}`;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Pure: build the failure message for the operator. Names the failing
|
|
239
|
-
* file(s) and states the rewrite-not-append contract explicitly.
|
|
240
|
-
*
|
|
241
|
-
* @param {number} epicId
|
|
242
|
-
* @param {Array<{ file: string, pass: boolean }>} [results]
|
|
243
|
-
*/
|
|
244
|
-
export function renderFreshnessFailureMessage(epicId, results = []) {
|
|
245
|
-
const failing = results.filter((r) => !r.pass).map((r) => r.file);
|
|
246
|
-
const fileList = failing.length > 0 ? failing.join(', ') : '(see rows above)';
|
|
247
|
-
return (
|
|
248
|
-
`[docs-freshness] ❌ Documentation freshness gate FAILED for Epic #${epicId}.\n\n` +
|
|
249
|
-
`Failing file(s): ${fileList}\n\n` +
|
|
250
|
-
`Living docs satisfy this gate by being REWRITTEN in an Epic-referencing ` +
|
|
251
|
-
`commit — a commit whose message references #${epicId} and touches the ` +
|
|
252
|
-
`file — NOT by appending a #${epicId} annotation to the body. The ` +
|
|
253
|
-
`body-annotation path passes ONLY for changelog-class files (basename ` +
|
|
254
|
-
`matches /changelog/i). Rewrite each failing file for the Epic, then ` +
|
|
255
|
-
`re-run /deliver.`
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/** Pure: success message. */
|
|
260
|
-
export function renderFreshnessSuccessMessage(epicId, count) {
|
|
261
|
-
return `[docs-freshness] ✅ All ${count} doc(s) reference Epic #${epicId}.`;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/* node:coverage disable -- process I/O + real config/git wiring (stdout,
|
|
265
|
-
process.exit, resolveConfig, runAsCli); the pure logic these thin wrappers
|
|
266
|
-
call is covered directly above. */
|
|
267
|
-
function reportEmptyDocs(epicId, json) {
|
|
268
|
-
if (json) {
|
|
269
|
-
process.stdout.write(
|
|
270
|
-
`${JSON.stringify({ ok: true, epicId, results: [] })}\n`,
|
|
271
|
-
);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
Logger.info(
|
|
275
|
-
`[docs-freshness] ⏭ No docs configured under delivery.docsFreshness.paths or ` +
|
|
276
|
-
`project.docsContextFiles — nothing to check.`,
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function reportGateOutcome({ epicId, json, ok, results }) {
|
|
281
|
-
if (json) {
|
|
282
|
-
process.stdout.write(`${JSON.stringify({ ok, epicId, results })}\n`);
|
|
283
|
-
if (!ok) process.exit(1);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
for (const r of results) Logger.info(renderFreshnessLine(r));
|
|
287
|
-
if (ok) {
|
|
288
|
-
Logger.info(renderFreshnessSuccessMessage(epicId, results.length));
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
Logger.error(renderFreshnessFailureMessage(epicId, results));
|
|
292
|
-
process.exit(1);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async function main() {
|
|
296
|
-
const args = parseFreshnessArgs(process.argv.slice(2));
|
|
297
|
-
if (args.epicId === null) {
|
|
298
|
-
throw new Error(
|
|
299
|
-
'Usage: node validate-docs-freshness.js --epic <EPIC_ID> [--docs a.md,b.md] [--json]',
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
const { epicId, json, docsList } = args;
|
|
303
|
-
const config = resolveConfig();
|
|
304
|
-
const docs = docsList ?? resolveDocList(config);
|
|
305
|
-
if (docs.length === 0) {
|
|
306
|
-
reportEmptyDocs(epicId, json);
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
const { ok, results } = runFreshnessGate({ epicId, docs });
|
|
310
|
-
reportGateOutcome({ epicId, json, ok, results });
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
runAsCli(import.meta.url, main, { source: 'validate-docs-freshness' });
|
|
314
|
-
/* node:coverage enable */
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: diagnose-friction
|
|
3
|
-
description: >-
|
|
4
|
-
Wrap a shell command with diagnostic capture. On failure, print static
|
|
5
|
-
suggestions and append a structured `friction` record to the per-Story
|
|
6
|
-
signals.ndjson stream. Use whenever a script in the orchestration loop
|
|
7
|
-
invokes a tool whose failure shape we want the analyzer to attribute.
|
|
8
|
-
allowed_tools:
|
|
9
|
-
- Bash
|
|
10
|
-
- Read
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# diagnose-friction
|
|
14
|
-
|
|
15
|
-
## Policy Capsule
|
|
16
|
-
|
|
17
|
-
- Invoke via the wrapping CLI `node .agents/scripts/diagnose-friction.js --story <id> [--epic <id>] --cmd <command args...>`; this is the single supported entry point.
|
|
18
|
-
- `--cmd` takes the command as **separate argv words** — the CLI spawns them with no shell. Quoting the whole command as one string (`--cmd "npm run lint"`) makes the entire string the executable name, so the spawn fails ENOENT. That is a usage error in your own invocation, not friction: the CLI refuses it with a usage message and appends **no** ledger row. Re-run it unquoted.
|
|
19
|
-
- Pass the wrapped command's stdout and stderr through **unchanged** — never reformat, redact, or buffer in a way that loses the original failure shape.
|
|
20
|
-
- Never mutate the wrapped command's exit code. The Skill observes; the caller decides whether the failure is fatal.
|
|
21
|
-
- Operate as **best-effort observation**: a write failure on the signals stream MUST NOT halt the runner. A missing signal is preferable to a stalled wave.
|
|
22
|
-
- On non-zero exit append a `friction` NDJSON record (`kind`, `ts`, `category`, `detail`, `exitCode`) only through the signals writer helper — never open `signals.ndjson` directly.
|
|
23
|
-
- Resolve Story context from `--story` (and `--epic` when a run id applies); there is no body-parsing fallback — pass the flags explicitly.
|
|
24
|
-
- Do **not** post GitHub comments from this Skill. Friction is local NDJSON: the retro is what aggregates the stream and routes recurring friction into proposals (Story #4545 deleted `analyze-execution`, the perf-summary comment surface).
|
|
25
|
-
- Categorize failures deterministically (rebase abort, test-suite name, lint category, etc.) so the retro can attribute friction without re-running the command.
|
|
26
|
-
|
|
27
|
-
## Role
|
|
28
|
-
|
|
29
|
-
Diagnostic interceptor. Captures the failure shape of a wrapped command
|
|
30
|
-
and persists it as a structured signal so the retro can attribute friction
|
|
31
|
-
back to the Story without re-running the command.
|
|
32
|
-
|
|
33
|
-
## When to use
|
|
34
|
-
|
|
35
|
-
Any orchestration call site whose failure mode is informative
|
|
36
|
-
(rebase aborts, test failures with classifiable suite names, lint
|
|
37
|
-
errors with stable categories). The wrapping script today is
|
|
38
|
-
`diagnose-friction.js`; this Skill documents the contract for callers
|
|
39
|
-
that want to dispatch via the Skill tool rather than spawn the CLI.
|
|
40
|
-
|
|
41
|
-
## Inputs
|
|
42
|
-
|
|
43
|
-
- `--cmd <command args...>` — the command to invoke and observe, passed as
|
|
44
|
-
separate argv words. A single whitespace-containing argument is rejected as
|
|
45
|
-
a quoting mistake before anything is spawned or recorded.
|
|
46
|
-
- `--story <id>` / `--epic <id>` (optional) — when resolved, the Skill
|
|
47
|
-
appends a `friction` signal to
|
|
48
|
-
`temp/run-<eid>/stories/story-<sid>/signals.ndjson` on non-zero exit
|
|
49
|
-
(standalone Stories: `temp/standalone/stories/story-<sid>/`).
|
|
50
|
-
|
|
51
|
-
## Outputs
|
|
52
|
-
|
|
53
|
-
- The wrapped command's stdout / stderr is passed through unchanged.
|
|
54
|
-
- On non-zero exit: a `friction` NDJSON record (kind, ts, category,
|
|
55
|
-
detail, exitCode) is appended via the signals writer.
|
|
56
|
-
- No GitHub comments are posted — friction is a local NDJSON signal. The
|
|
57
|
-
retro reads the stream out-of-band.
|
|
58
|
-
|
|
59
|
-
## Procedure
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
node .agents/scripts/diagnose-friction.js \
|
|
63
|
-
--story <id> [--epic <id>] \
|
|
64
|
-
--cmd <command args...>
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
The Skill's contract is "best-effort observation" — the wrapping
|
|
68
|
-
script never halts the runner because of a write failure. A missing
|
|
69
|
-
signal is preferable to a halted runner.
|
|
70
|
-
|
|
71
|
-
## Constraints
|
|
72
|
-
|
|
73
|
-
- Do **not** post GitHub comments from this Skill. Friction is local
|
|
74
|
-
NDJSON; the retro owns the aggregate surface.
|
|
75
|
-
- Do **not** mutate the wrapped command's exit code. The Skill's job
|
|
76
|
-
is observation; the caller decides whether the failure is fatal.
|
|
77
|
-
- Do **not** open `signals.ndjson` directly — use the signals writer
|
|
78
|
-
helper so the file format and warn-once policy stay consistent.
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: >-
|
|
3
|
-
Helper doc for the signals-view.js debug viewer. Renders the signals
|
|
4
|
-
span-tree for a run (and optionally a single Story) to the terminal.
|
|
5
|
-
Read-only over `lib/signals/` — no remote writes, no state mutation, no
|
|
6
|
-
auto-fixes. Dumb-terminal-safe (`console.log` only). Not a slash command —
|
|
7
|
-
invoke the script directly when needed.
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# signals span-tree viewer (helper)
|
|
11
|
-
|
|
12
|
-
> **Helper, not a slash command.** Files under `workflows/helpers/` are not
|
|
13
|
-
> projected into the mandrel plugin command tree. The signals subsystem itself
|
|
14
|
-
> (`lib/signals/`, writer, schema, detectors, NDJSON listeners) runs as
|
|
15
|
-
> part of the normal `/deliver` machinery — this viewer is for
|
|
16
|
-
> ad-hoc debugging when you need to inspect the span-tree directly.
|
|
17
|
-
> Invoke the backing script: `node .agents/scripts/signals-view.js <run-id> [--story <id>]`.
|
|
18
|
-
|
|
19
|
-
## Overview
|
|
20
|
-
|
|
21
|
-
`signals-view.js` is the operator-facing viewer for the consolidated
|
|
22
|
-
signals stream. It reads `temp/run-<id>/stories/story-<sid>/signals.ndjson` via
|
|
23
|
-
[`lib/signals/read`](../../scripts/lib/signals/read.js), builds an
|
|
24
|
-
in-memory span-tree via
|
|
25
|
-
[`lib/signals/buildSpanTree`](../../scripts/lib/signals/span-tree.js), and
|
|
26
|
-
prints a run → Story → events tree to stdout.
|
|
27
|
-
|
|
28
|
-
It is distinct from `diagnose-friction.js` (per-Story signal capture that
|
|
29
|
-
wraps a shell command) and from `diagnose.js` (read of the checks
|
|
30
|
-
registry). This viewer is purely a formatter over an iterator — no
|
|
31
|
-
GitHub I/O, no commit creation, no label transitions.
|
|
32
|
-
|
|
33
|
-
```text
|
|
34
|
-
node .agents/scripts/signals-view.js <run-id> [--story <id>]
|
|
35
|
-
→ signals.read({ run, story? })
|
|
36
|
-
→ buildSpanTree(asyncIterator)
|
|
37
|
-
→ console.log lines (run → Story → events)
|
|
38
|
-
→ exit 0 (always; missing-file path prints a friendly message)
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Arguments
|
|
42
|
-
|
|
43
|
-
| Argument | Required | Description |
|
|
44
|
-
| ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
-
| `<run-id>` | yes | Positive integer run ID. The viewer resolves `temp/run-<id>/` under the configured `project.paths.tempRoot`. |
|
|
46
|
-
| `--story <id>` | no | Positive integer Story ID. When set, the printed tree is narrowed to a single Story subtree under the run. |
|
|
47
|
-
|
|
48
|
-
## Flags (test-only)
|
|
49
|
-
|
|
50
|
-
| Flag | Description |
|
|
51
|
-
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
-
| `--temp-root <path>` | Override the resolved `tempRoot`. Reserved for fixture-driven tests (see `tests/signals-view.test.js`); production callers omit this flag. |
|
|
53
|
-
|
|
54
|
-
## Exit codes
|
|
55
|
-
|
|
56
|
-
| Code | Meaning |
|
|
57
|
-
| ---- | --------------------------------------------------------------------------------------------- |
|
|
58
|
-
| `0` | Happy path **or** missing signals file. The missing-file path prints a friendly message ("No signals found for run #N"), never a stack trace. |
|
|
59
|
-
| `1` | Bad arguments (non-integer `<run-id>`, missing positional, malformed `--story`). |
|
|
60
|
-
|
|
61
|
-
## Examples
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
# Render every Story under run #1181.
|
|
65
|
-
node .agents/scripts/signals-view.js 1181
|
|
66
|
-
|
|
67
|
-
# Narrow to a single Story subtree.
|
|
68
|
-
node .agents/scripts/signals-view.js 1181 --story 1438
|
|
69
|
-
|
|
70
|
-
# No signals yet — friendly message, exit 0.
|
|
71
|
-
node .agents/scripts/signals-view.js 9999
|
|
72
|
-
# → No signals found for run #9999.
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Phase steps
|
|
76
|
-
|
|
77
|
-
1. Parse `<run-id>` and optional `--story <id>` from the CLI
|
|
78
|
-
arguments. Reject non-integer or non-positive inputs with exit 1 and
|
|
79
|
-
the canonical usage line.
|
|
80
|
-
2. Invoke `node .agents/scripts/signals-view.js <run-id> [--story <id>]`
|
|
81
|
-
from the operator's working directory. The script resolves
|
|
82
|
-
`tempRoot` from the project's `.agentrc.json` (or the framework
|
|
83
|
-
default `'temp'`).
|
|
84
|
-
3. Read the Story-tree output as the canonical surface — no further
|
|
85
|
-
parsing is needed; the script's stdout is the deliverable.
|
|
86
|
-
|
|
87
|
-
## Constraints
|
|
88
|
-
|
|
89
|
-
- **Never** writes to GitHub state (no label transitions, no comments).
|
|
90
|
-
The viewer is purely read-side.
|
|
91
|
-
- **Never** mutates the working tree, including `temp/`. The on-disk
|
|
92
|
-
signals files are owned by `signals-writer.js`; this viewer only
|
|
93
|
-
reads them.
|
|
94
|
-
- **Never** uses Ink, blessed, or terminal-control escape sequences.
|
|
95
|
-
Output goes through `console.log` exclusively so the viewer works on
|
|
96
|
-
Windows + bash hosts (see [`helpers/parallel-tooling.md`](parallel-tooling.md)).
|
|
97
|
-
- **Always** honours the configured `project.paths.tempRoot`.
|
|
98
|
-
Earlier post-merge work leaked to the real repo root regardless of
|
|
99
|
-
test sandbox `tempRoot` (project memory: `phase_timings_uses_project_root`) —
|
|
100
|
-
this viewer reads via [`lib/config/temp-paths.js`](../../scripts/lib/config/temp-paths.js)
|
|
101
|
-
so the sandbox path always wins.
|
|
102
|
-
|
|
103
|
-
## See also
|
|
104
|
-
|
|
105
|
-
- [`.agents/scripts/signals-view.js`](../../scripts/signals-view.js) — the
|
|
106
|
-
CLI implementation backing this helper.
|
|
107
|
-
- [`.agents/scripts/lib/signals/`](../../scripts/lib/signals/) — the
|
|
108
|
-
shared reader + schema + span-tree barrel.
|
|
109
|
-
- [`tests/signals-view.test.js`](https://github.com/dsj1984/mandrel/blob/main/tests/signals-view.test.js) —
|
|
110
|
-
pinned output and tempRoot-honour contracts.
|
|
111
|
-
- [`tests/lib/signals/span-tree.test.js`](https://github.com/dsj1984/mandrel/blob/main/tests/lib/signals/span-tree.test.js) —
|
|
112
|
-
pure-function contract for the span-tree builder.
|