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,57 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ticket-lease.js — assignee-as-lease primitive (Story #3480, Epic #3457).
|
|
3
3
|
*
|
|
4
|
-
* The workflow-guards Feature (#3478) needs a way for one operator to take
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* compared against a configured TTL.
|
|
4
|
+
* The workflow-guards Feature (#3478) needs a way for one operator to take an
|
|
5
|
+
* exclusive claim on a ticket so two concurrent runs do not both drive the
|
|
6
|
+
* same Story. Rather than invent a new state column, the lease rides the
|
|
7
|
+
* ticket's existing **assignees** surface: the single assignee *is* the lease
|
|
8
|
+
* owner.
|
|
10
9
|
*
|
|
11
|
-
* **
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
16
|
-
* which
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
10
|
+
* **A foreign claim is a refusal, full stop (Story #5006).** The lease shipped
|
|
11
|
+
* with a TTL: a claim whose owner's last heartbeat was older than
|
|
12
|
+
* `delivery.lease.ttlMs` counted as stale and was silently reclaimed. The
|
|
13
|
+
* `story.heartbeat` emitter that would have fed it was structurally inert (it
|
|
14
|
+
* demanded an `epicId >= 1` that v2, which has no Epics, never supplies) and
|
|
15
|
+
* was deleted, after which every guard pinned `heartbeatAt` to `now` so the
|
|
16
|
+
* liveness test always answered "live" — the TTL, the heartbeat parameter and
|
|
17
|
+
* the reclaim branch were an elaborate way of writing `true`. They are gone.
|
|
18
|
+
* A stranded claim is cleared with `--steal`, which is now the only way past
|
|
19
|
+
* a foreign owner.
|
|
21
20
|
*
|
|
22
|
-
* The
|
|
21
|
+
* The two exported operations are deliberately thin and provider-agnostic:
|
|
23
22
|
*
|
|
24
23
|
* - `acquireLease` — claim an unassigned ticket, re-affirm a self-held
|
|
25
|
-
* claim,
|
|
26
|
-
*
|
|
27
|
-
* `steal: true` — forcibly transfer a *live* foreign
|
|
28
|
-
* claim.
|
|
24
|
+
* claim, or — with `steal: true` — forcibly transfer a
|
|
25
|
+
* foreign claim.
|
|
29
26
|
* - `releaseLease` — clear the assignment, but only when the operator
|
|
30
27
|
* still holds it (a no-op once the ticket was
|
|
31
28
|
* reassigned elsewhere, so a late release never steals
|
|
32
29
|
* a claim back from whoever took over).
|
|
33
|
-
* - `describeLease` — a read-only snapshot of the current claim and its
|
|
34
|
-
* liveness, used by callers (and tests) to reason about
|
|
35
|
-
* a ticket without mutating it.
|
|
36
30
|
*
|
|
37
31
|
* Provider contract (a subset of `ITicketingProvider`):
|
|
38
32
|
* - `getTicket(id)` → `{ assignees: string[], ... }`
|
|
39
33
|
* - `updateTicket(id, { assignees })` writes the assignee list.
|
|
40
|
-
*
|
|
41
|
-
* Liveness seam: callers supply the owner's last-heartbeat epoch-ms via the
|
|
42
|
-
* `heartbeatAt` option (a number, or `null`/`undefined` when no heartbeat has
|
|
43
|
-
* ever been recorded for the current owner). Threading the timestamp in keeps
|
|
44
|
-
* this module pure and trivially unit-testable — it does not read any ledger
|
|
45
|
-
* itself. A claim with no heartbeat is treated as stale (reclaimable) by
|
|
46
|
-
* `isClaimLive`; note the live guards never take that branch, per the
|
|
47
|
-
* fail-closed anchoring described above.
|
|
48
|
-
*
|
|
49
|
-
* `now` is injectable (epoch ms) for deterministic tests; it defaults to
|
|
50
|
-
* `Date.now()`.
|
|
51
34
|
*/
|
|
52
35
|
|
|
53
|
-
import { resolveLeaseTtlMs } from '../config/limits.js';
|
|
54
|
-
|
|
55
36
|
/**
|
|
56
37
|
* The shipped, non-personal operator-identity placeholder (and its bare,
|
|
57
38
|
* post-normalise form). The committed `.agentrc.json` and the distributed
|
|
@@ -94,35 +75,6 @@ export function normalizeOperatorHandle(raw) {
|
|
|
94
75
|
return trimmed;
|
|
95
76
|
}
|
|
96
77
|
|
|
97
|
-
/**
|
|
98
|
-
* Decide whether a foreign claim is still "live" given the owner's last
|
|
99
|
-
* heartbeat and the configured TTL. A claim is live when a heartbeat exists
|
|
100
|
-
* and is no older than `ttlMs`. A missing heartbeat (`null`/`undefined`) or a
|
|
101
|
-
* heartbeat older than the TTL is stale and therefore reclaimable.
|
|
102
|
-
*
|
|
103
|
-
* The `heartbeatAt` seam is retained, but there is no longer any in-repo
|
|
104
|
-
* heartbeat *source*: the `story.heartbeat` emitter was structurally inert
|
|
105
|
-
* (it required an `epicId >= 1` that v2 never sets) and was deleted along
|
|
106
|
-
* with the ledger reader that scanned for it. Every live caller reaches this
|
|
107
|
-
* through `lease-guard-shared.acquireLeaseFailClosed` with
|
|
108
|
-
* `anchorHeartbeatToNow`, which pins `heartbeatAt` to `now` so ANY foreign
|
|
109
|
-
* claim reads live and the guard fails closed — a stranded claim is cleared
|
|
110
|
-
* with `--steal`, not by TTL expiry. The parameter stays because that
|
|
111
|
-
* anchoring is expressed through it.
|
|
112
|
-
*
|
|
113
|
-
* @param {object} args
|
|
114
|
-
* @param {number|null|undefined} args.heartbeatAt Owner's last heartbeat (epoch ms).
|
|
115
|
-
* @param {number} args.ttlMs Lease TTL in milliseconds.
|
|
116
|
-
* @param {number} args.now Current time (epoch ms).
|
|
117
|
-
* @returns {boolean}
|
|
118
|
-
*/
|
|
119
|
-
export function isClaimLive({ heartbeatAt, ttlMs, now }) {
|
|
120
|
-
if (typeof heartbeatAt !== 'number' || !Number.isFinite(heartbeatAt)) {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
return now - heartbeatAt <= ttlMs;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
78
|
/**
|
|
127
79
|
* Normalise the assignee list into a single current owner. The lease model is
|
|
128
80
|
* single-holder: the first assignee is authoritative. Returns `null` for an
|
|
@@ -141,10 +93,10 @@ export function currentOwner(assignees) {
|
|
|
141
93
|
*
|
|
142
94
|
* @param {string} op
|
|
143
95
|
* @param {object} opts
|
|
144
|
-
* @returns {{ provider: object, ticketId: number, operator: string
|
|
96
|
+
* @returns {{ provider: object, ticketId: number, operator: string }}
|
|
145
97
|
*/
|
|
146
98
|
function normaliseOpts(op, opts) {
|
|
147
|
-
const { provider, ticketId, operator
|
|
99
|
+
const { provider, ticketId, operator } = opts ?? {};
|
|
148
100
|
|
|
149
101
|
if (!provider || typeof provider.getTicket !== 'function') {
|
|
150
102
|
throw new Error(`${op}: provider with getTicket/updateTicket is required`);
|
|
@@ -156,45 +108,7 @@ function normaliseOpts(op, opts) {
|
|
|
156
108
|
throw new Error(`${op}: operator must be a non-empty string`);
|
|
157
109
|
}
|
|
158
110
|
|
|
159
|
-
|
|
160
|
-
const resolvedNow =
|
|
161
|
-
typeof now === 'number' && Number.isFinite(now) ? now : Date.now();
|
|
162
|
-
|
|
163
|
-
return { provider, ticketId, operator, ttlMs, now: resolvedNow };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Read-only snapshot of the current lease on a ticket. Never mutates.
|
|
168
|
-
*
|
|
169
|
-
* @param {object} opts
|
|
170
|
-
* @param {object} opts.provider Ticketing provider (getTicket).
|
|
171
|
-
* @param {number} opts.ticketId Ticket to inspect.
|
|
172
|
-
* @param {string} opts.operator The operator asking.
|
|
173
|
-
* @param {number|null} [opts.heartbeatAt] Owner's last heartbeat (epoch ms).
|
|
174
|
-
* @param {object} [opts.config] Resolved config (for TTL default).
|
|
175
|
-
* @param {number} [opts.ttlMs] Explicit TTL override (epoch ms).
|
|
176
|
-
* @param {number} [opts.now] Injectable clock (epoch ms).
|
|
177
|
-
* @returns {Promise<{
|
|
178
|
-
* ticketId: number,
|
|
179
|
-
* owner: string|null,
|
|
180
|
-
* heldByOperator: boolean,
|
|
181
|
-
* live: boolean,
|
|
182
|
-
* ttlMs: number,
|
|
183
|
-
* }>}
|
|
184
|
-
*/
|
|
185
|
-
export async function describeLease(opts) {
|
|
186
|
-
const { provider, ticketId, operator, ttlMs, now } = normaliseOpts(
|
|
187
|
-
'describeLease',
|
|
188
|
-
opts,
|
|
189
|
-
);
|
|
190
|
-
const ticket = await provider.getTicket(ticketId);
|
|
191
|
-
const owner = currentOwner(ticket?.assignees);
|
|
192
|
-
const heldByOperator = owner === operator;
|
|
193
|
-
const live =
|
|
194
|
-
owner === null
|
|
195
|
-
? false
|
|
196
|
-
: isClaimLive({ heartbeatAt: opts.heartbeatAt, ttlMs, now });
|
|
197
|
-
return { ticketId, owner, heldByOperator, live, ttlMs };
|
|
111
|
+
return { provider, ticketId, operator };
|
|
198
112
|
}
|
|
199
113
|
|
|
200
114
|
/**
|
|
@@ -205,10 +119,8 @@ export async function describeLease(opts) {
|
|
|
205
119
|
* `reason: 'unclaimed'`.
|
|
206
120
|
* - Operator already holds it → no write, `acquired: true`,
|
|
207
121
|
* `reason: 'already-held'`.
|
|
208
|
-
* -
|
|
122
|
+
* - Foreign claim, no steal → no write, `acquired: false`,
|
|
209
123
|
* `owner: <foreign>`, `reason: 'held'`.
|
|
210
|
-
* - Stale foreign claim → reassign operator, `acquired: true`,
|
|
211
|
-
* `reason: 'reclaimed'`.
|
|
212
124
|
* - Foreign claim + `steal:true` → reassign operator, `acquired: true`,
|
|
213
125
|
* `reason: 'stolen'`.
|
|
214
126
|
* - Lost a write race → a foreign login co-assigned between our
|
|
@@ -226,23 +138,16 @@ export async function describeLease(opts) {
|
|
|
226
138
|
* @param {object} opts.provider Ticketing provider.
|
|
227
139
|
* @param {number} opts.ticketId Ticket to claim.
|
|
228
140
|
* @param {string} opts.operator Operator acquiring the lease.
|
|
229
|
-
* @param {
|
|
230
|
-
* @param {boolean} [opts.steal=false] Transfer a live foreign claim.
|
|
231
|
-
* @param {object} [opts.config] Resolved config (TTL default).
|
|
232
|
-
* @param {number} [opts.ttlMs] Explicit TTL override.
|
|
233
|
-
* @param {number} [opts.now] Injectable clock.
|
|
141
|
+
* @param {boolean} [opts.steal=false] Transfer a foreign claim.
|
|
234
142
|
* @returns {Promise<{
|
|
235
143
|
* acquired: boolean,
|
|
236
144
|
* owner: string,
|
|
237
145
|
* previousOwner: string|null,
|
|
238
|
-
* reason: 'unclaimed'|'already-held'|'
|
|
146
|
+
* reason: 'unclaimed'|'already-held'|'stolen'|'held'|'lost-race',
|
|
239
147
|
* }>}
|
|
240
148
|
*/
|
|
241
149
|
export async function acquireLease(opts) {
|
|
242
|
-
const { provider, ticketId, operator
|
|
243
|
-
'acquireLease',
|
|
244
|
-
opts,
|
|
245
|
-
);
|
|
150
|
+
const { provider, ticketId, operator } = normaliseOpts('acquireLease', opts);
|
|
246
151
|
const steal = opts.steal === true;
|
|
247
152
|
|
|
248
153
|
const ticket = await provider.getTicket(ticketId);
|
|
@@ -269,9 +174,8 @@ export async function acquireLease(opts) {
|
|
|
269
174
|
};
|
|
270
175
|
}
|
|
271
176
|
|
|
272
|
-
// Foreign claim —
|
|
273
|
-
|
|
274
|
-
if (live && !steal) {
|
|
177
|
+
// Foreign claim — refuse unless the operator explicitly steals it.
|
|
178
|
+
if (!steal) {
|
|
275
179
|
return {
|
|
276
180
|
acquired: false,
|
|
277
181
|
owner,
|
|
@@ -285,7 +189,7 @@ export async function acquireLease(opts) {
|
|
|
285
189
|
ticketId,
|
|
286
190
|
operator,
|
|
287
191
|
previousOwner: owner,
|
|
288
|
-
reason:
|
|
192
|
+
reason: 'stolen',
|
|
289
193
|
});
|
|
290
194
|
}
|
|
291
195
|
|
|
@@ -359,9 +263,6 @@ async function claimAndVerify({
|
|
|
359
263
|
* @param {object} opts.provider Ticketing provider.
|
|
360
264
|
* @param {number} opts.ticketId Ticket to release.
|
|
361
265
|
* @param {string} opts.operator Operator releasing the lease.
|
|
362
|
-
* @param {object} [opts.config] Resolved config (TTL default).
|
|
363
|
-
* @param {number} [opts.ttlMs] Explicit TTL override.
|
|
364
|
-
* @param {number} [opts.now] Injectable clock.
|
|
365
266
|
* @returns {Promise<{
|
|
366
267
|
* released: boolean,
|
|
367
268
|
* owner: string|null,
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { parse as parseStoryBody } from '../story-body/story-body.js';
|
|
32
|
-
import { estimateTokens } from './
|
|
32
|
+
import { estimateTokens } from './spec-spill.js';
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Normalize a Story's `body` to the structured object the sizing layers
|
|
@@ -146,14 +146,14 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
146
146
|
// `--steal`, not by TTL expiry. One entry per ticket; re-acquires upsert
|
|
147
147
|
// in place.
|
|
148
148
|
'plan-lease',
|
|
149
|
-
// Story #4415 (Epic #4406) — the feedback-loop
|
|
150
|
-
// (`
|
|
151
|
-
// `cross-repo-deferred` comment
|
|
149
|
+
// Story #4415 (Epic #4406) — the feedback-loop graduator
|
|
150
|
+
// (`retro-proposals-graduator.js`) upserts a
|
|
151
|
+
// `cross-repo-deferred` comment listing findings that route
|
|
152
152
|
// to a different repository and were therefore not filed here. Replaces
|
|
153
153
|
// the prior log-line-only trace so the deferral survives the finalize
|
|
154
154
|
// run as a durable, operator-visible record. Discriminated by a
|
|
155
|
-
// `graduator="
|
|
156
|
-
//
|
|
155
|
+
// `graduator="<name>"` attr so independent graduators do not clobber each
|
|
156
|
+
// other's comment; re-runs upsert in place.
|
|
157
157
|
'cross-repo-deferred',
|
|
158
158
|
// Epic #4474 (PR3) / v2 Stage 3 — `plan-persist.js` upserts a single
|
|
159
159
|
// `plan-summary` comment on the primary Story at terminal persist
|
|
@@ -393,12 +393,13 @@ async function emitBlockedFriction(ticketId, fromState, newState, opts) {
|
|
|
393
393
|
* `agent::done` at story-close (after the merge lands), not when the
|
|
394
394
|
* last Task commit landed on the still-unmerged Story branch. The
|
|
395
395
|
* parameter is preserved for callers that still suppress cascade
|
|
396
|
-
* explicitly (
|
|
396
|
+
* explicitly (`single-story-init.js`, and `bulk.js` where the upward
|
|
397
|
+
* recursion is driven by hand instead).
|
|
397
398
|
*
|
|
398
399
|
* `ticketSnapshot` (Story #1795 / Epic #1788) is an optional pre-fetched
|
|
399
|
-
* ticket object. When the caller already holds the ticket
|
|
400
|
-
*
|
|
401
|
-
*
|
|
400
|
+
* ticket object. When the caller already holds the ticket — e.g. it
|
|
401
|
+
* hydrated the ticket to inspect its labels first — passing the
|
|
402
|
+
* snapshot eliminates the two `getTicket` round-trips that
|
|
402
403
|
* `transitionTicketState` would otherwise issue — one for the notify
|
|
403
404
|
* `fromState` lookup and one inside `provider.updateTicket`'s label
|
|
404
405
|
* merge path. Backwards compatible: when omitted, behaviour is unchanged.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* verify-tier-repair.js — repair-before-judging for the `verify[]` tier suffix
|
|
3
|
+
* (Story #5005).
|
|
4
|
+
*
|
|
5
|
+
* The tier suffix (`… (unit)`) is a deterministic, mechanically-derivable
|
|
6
|
+
* formality. `task-body-validator.js` already computed the corrected entry in
|
|
7
|
+
* order to print it as a "Suggested fix" — then rejected the plan anyway and
|
|
8
|
+
* charged the author a full re-drafting round to paste that exact string back.
|
|
9
|
+
* This module applies the inference the validator already trusts, so the hard
|
|
10
|
+
* error is reserved for the entries only the author can resolve.
|
|
11
|
+
*
|
|
12
|
+
* It lives beside the validator rather than inside it because the validator's
|
|
13
|
+
* job is to *judge*: mixing a mutating repair pass into a module of pure
|
|
14
|
+
* collectors muddies both. `persist-helpers.js#validateTickets` calls this
|
|
15
|
+
* first, then `validateTaskBodies`.
|
|
16
|
+
*
|
|
17
|
+
* @module lib/orchestration/verify-tier-repair
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { suggestVerifyFix } from '../story-body/body-format-lints.js';
|
|
21
|
+
import {
|
|
22
|
+
parse as parseStoryBody,
|
|
23
|
+
serialize as serializeStoryBody,
|
|
24
|
+
} from '../story-body/story-body.js';
|
|
25
|
+
import { VERIFY_TIER_VALUES } from './task-body-validator.js';
|
|
26
|
+
|
|
27
|
+
/** A parenthesised tier drawn from the canonical vocabulary, at end of entry. */
|
|
28
|
+
const VERIFY_TIER_RE = new RegExp(
|
|
29
|
+
`\\((?:${VERIFY_TIER_VALUES.join('|')})\\)\\s*$`,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Rewrite one `verify[]` list, appending the inferable testing tier to any
|
|
34
|
+
* entry missing one. Returns `null` when nothing changed, so callers can keep
|
|
35
|
+
* an already-compliant list byte-identical (and skip re-serializing the body
|
|
36
|
+
* it came from).
|
|
37
|
+
*
|
|
38
|
+
* @param {unknown} rawVerify
|
|
39
|
+
* @returns {string[]|null} The corrected list, or `null` when no fix applied.
|
|
40
|
+
*/
|
|
41
|
+
function repairVerifyList(rawVerify) {
|
|
42
|
+
if (!Array.isArray(rawVerify)) return null;
|
|
43
|
+
let changed = false;
|
|
44
|
+
const next = rawVerify.map((entry) => {
|
|
45
|
+
if (typeof entry !== 'string') return entry;
|
|
46
|
+
if (entry.startsWith('manual:') || VERIFY_TIER_RE.test(entry)) return entry;
|
|
47
|
+
const fix = suggestVerifyFix(entry);
|
|
48
|
+
if (fix === null) return entry;
|
|
49
|
+
changed = true;
|
|
50
|
+
return fix;
|
|
51
|
+
});
|
|
52
|
+
return changed ? next : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Repair the `## Verify` section of a serialized (string) body, in place on
|
|
57
|
+
* the ticket. Only re-serializes when a fix actually applied, so a compliant
|
|
58
|
+
* body is never round-tripped through `parse → serialize`.
|
|
59
|
+
*
|
|
60
|
+
* Leaving the body untouched while the top level was repaired would be worse
|
|
61
|
+
* than not repairing at all: `syncContractFieldFromTopLevel` fails closed on a
|
|
62
|
+
* body section that disagrees with its top-level array, so both sides must be
|
|
63
|
+
* repaired by the same rule or neither.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} ticket
|
|
66
|
+
*/
|
|
67
|
+
function repairStringBodyVerify(ticket) {
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = parseStoryBody(ticket.body).body;
|
|
71
|
+
} catch {
|
|
72
|
+
// An unparseable body is already a hard error in `validateTaskBodyShape`;
|
|
73
|
+
// there is nothing to repair and nothing to report twice.
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const repaired = repairVerifyList(parsed.verify);
|
|
77
|
+
if (repaired === null) return;
|
|
78
|
+
ticket.body = serializeStoryBody({ ...parsed, verify: repaired });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Auto-append the testing tier to `verify[]` entries that omit one, wherever
|
|
83
|
+
* `suggestVerifyFix` can infer it from the command. An entry whose tier cannot
|
|
84
|
+
* be inferred is left untouched and still hard-errors downstream.
|
|
85
|
+
*
|
|
86
|
+
* Mutates `tickets` in place (the persist pipeline threads this same array on
|
|
87
|
+
* to assembly) and returns it. Already-compliant tickets are untouched, so
|
|
88
|
+
* their persisted output stays byte-identical. Total — a non-array argument
|
|
89
|
+
* and non-Story tickets are no-ops.
|
|
90
|
+
*
|
|
91
|
+
* @param {object[]} tickets
|
|
92
|
+
* @returns {object[]} `tickets`
|
|
93
|
+
*/
|
|
94
|
+
export function normalizeVerifyTiers(tickets) {
|
|
95
|
+
for (const ticket of Array.isArray(tickets) ? tickets : []) {
|
|
96
|
+
if (!ticket || ticket.type !== 'story' || ticket.body == null) continue;
|
|
97
|
+
const topLevel = repairVerifyList(ticket.verify);
|
|
98
|
+
if (topLevel !== null) ticket.verify = topLevel;
|
|
99
|
+
if (typeof ticket.body === 'string') {
|
|
100
|
+
repairStringBodyVerify(ticket);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const bodyLevel = repairVerifyList(ticket.body.verify);
|
|
104
|
+
if (bodyLevel !== null) ticket.body.verify = bodyLevel;
|
|
105
|
+
}
|
|
106
|
+
return tickets;
|
|
107
|
+
}
|
|
@@ -34,17 +34,12 @@
|
|
|
34
34
|
// operator-facing string explaining the verdict (always populated,
|
|
35
35
|
// including for present tiers).
|
|
36
36
|
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// }
|
|
41
|
-
//
|
|
42
|
-
// Maps each acceptance criterion to its per-tier `coverageVerdict`, giving
|
|
43
|
-
// the AC × test-tier matrix the markdown report (lib/qa/coverage-report.js)
|
|
44
|
-
// renders.
|
|
37
|
+
// Story #5008 removed the `acceptanceMatrix` feeder and the markdown report it
|
|
38
|
+
// fed: the QA workflows read the per-tier verdict and author the missing-test
|
|
39
|
+
// prose directly, so the AC x tier matrix was a round-trip with no reader.
|
|
45
40
|
|
|
46
|
-
/** The three test tiers, in pyramid order (base
|
|
47
|
-
|
|
41
|
+
/** The three test tiers, in pyramid order (base -> top). */
|
|
42
|
+
const TIERS = Object.freeze(['unit', 'contract', 'acceptance']);
|
|
48
43
|
|
|
49
44
|
const PRESENT = 'present';
|
|
50
45
|
const ABSENT = 'absent';
|
|
@@ -217,80 +212,3 @@ export function coverageVerdict(surface = {}) {
|
|
|
217
212
|
|
|
218
213
|
return verdict;
|
|
219
214
|
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Normalize a single criterion descriptor into `{id, label, surface}`. A
|
|
223
|
-
* descriptor may carry `id`, `label` (falls back to id), and either a nested
|
|
224
|
-
* `surface` or a flat `{symbol, tests}` shape.
|
|
225
|
-
*/
|
|
226
|
-
function normalizeOne(entry, index) {
|
|
227
|
-
const e = entry && typeof entry === 'object' ? entry : {};
|
|
228
|
-
const id =
|
|
229
|
-
typeof e.id === 'string' && e.id.trim() !== ''
|
|
230
|
-
? e.id.trim()
|
|
231
|
-
: `AC-${index + 1}`;
|
|
232
|
-
const label =
|
|
233
|
-
typeof e.label === 'string' && e.label.trim() !== '' ? e.label.trim() : id;
|
|
234
|
-
|
|
235
|
-
let surface;
|
|
236
|
-
if (e.surface && typeof e.surface === 'object') {
|
|
237
|
-
surface = e.surface;
|
|
238
|
-
} else if (Array.isArray(e.tests) || typeof e.symbol === 'string') {
|
|
239
|
-
surface = { symbol: e.symbol, tests: e.tests };
|
|
240
|
-
} else {
|
|
241
|
-
surface = {};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return { id, label, surface };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Normalize the criteria input for {@link acceptanceMatrix}. Accepts either an
|
|
249
|
-
* array of criterion descriptors or a plain object keyed by criterion id whose
|
|
250
|
-
* values are surfaces (or `{surface}` wrappers). Returns a normalized array of
|
|
251
|
-
* `{id, label, surface}`.
|
|
252
|
-
*
|
|
253
|
-
* @param {Array<object>|object} criteria
|
|
254
|
-
* @returns {Array<{id:string,label:string,surface:object}>}
|
|
255
|
-
*/
|
|
256
|
-
function normalizeCriteria(criteria) {
|
|
257
|
-
if (Array.isArray(criteria)) {
|
|
258
|
-
return criteria.map((entry, index) => normalizeOne(entry, index));
|
|
259
|
-
}
|
|
260
|
-
if (criteria && typeof criteria === 'object') {
|
|
261
|
-
return Object.entries(criteria).map(([id, value], index) =>
|
|
262
|
-
normalizeOne(
|
|
263
|
-
value && typeof value === 'object' && !Array.isArray(value)
|
|
264
|
-
? { id, ...value }
|
|
265
|
-
: { id, surface: { tests: value } },
|
|
266
|
-
index,
|
|
267
|
-
),
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
throw new TypeError(
|
|
271
|
-
'acceptanceMatrix: criteria must be an array or an object',
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Build the AC × test-tier matrix: for each acceptance criterion, the per-tier
|
|
277
|
-
* {@link coverageVerdict}. This is the structured shape the markdown report
|
|
278
|
-
* (`lib/qa/coverage-report.js`) renders into a table.
|
|
279
|
-
*
|
|
280
|
-
* @param {Array<{id?:string,label?:string,surface?:object,symbol?:string,tests?:Array}>|Record<string,object>} criteria
|
|
281
|
-
* Acceptance criteria, either as an array of descriptors or an object keyed
|
|
282
|
-
* by criterion id. Each descriptor names a surface (nested `surface`, or a
|
|
283
|
-
* flat `{symbol, tests}`).
|
|
284
|
-
* @returns {{tiers: ReadonlyArray<string>,
|
|
285
|
-
* rows: Array<{id:string,label:string,
|
|
286
|
-
* verdict:ReturnType<typeof coverageVerdict>}>}}
|
|
287
|
-
*/
|
|
288
|
-
export function acceptanceMatrix(criteria) {
|
|
289
|
-
const normalized = normalizeCriteria(criteria);
|
|
290
|
-
const rows = normalized.map(({ id, label, surface }) => ({
|
|
291
|
-
id,
|
|
292
|
-
label,
|
|
293
|
-
verdict: coverageVerdict(surface),
|
|
294
|
-
}));
|
|
295
|
-
return { tiers: TIERS, rows };
|
|
296
|
-
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* detectors/common.js — shared helpers for the signals layer.
|
|
3
3
|
*
|
|
4
4
|
* Hoisted out of the detector modules (retry, rework) plus
|
|
5
|
-
* `signals/read.js
|
|
5
|
+
* the since-deleted `signals/read.js`, and `signals/schema.js`, all of which shipped
|
|
6
6
|
* byte-equivalent copies of these predicates. See Story #2464.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `lib/signals/` barrel.
|
|
3
3
|
*
|
|
4
|
-
* Consumers do `import {
|
|
4
|
+
* Consumers do `import { schema, appendSignal } from './lib/signals/index.js'`
|
|
5
5
|
* (or `import * as signals from './lib/signals/index.js'`) so a future
|
|
6
6
|
* shape migration only has to touch this file.
|
|
7
7
|
*
|
|
@@ -9,18 +9,20 @@
|
|
|
9
9
|
* - Epic #1181 / Story #1438 / Task #1459 — initial barrel with `read`
|
|
10
10
|
* + `schema`; `buildSpanTree` was a throwing placeholder.
|
|
11
11
|
* - Epic #1181 / Story #1440 / Task #1461 — placeholder replaced by
|
|
12
|
-
* the real
|
|
12
|
+
* the real span-tree builder.
|
|
13
13
|
* - Story #1476 — `appendSignal` / `appendTrace` re-exported here
|
|
14
14
|
* via `./write.js` so the gate scripts (and any
|
|
15
15
|
* new code) converge on `lib/signals/` instead of importing the
|
|
16
16
|
* writer directly from `lib/observability/`.
|
|
17
|
+
* - Story #5003 — the reader half (`read`, `buildSpanTree`) went with the
|
|
18
|
+
* debug viewer that consumed it: it walked a `run-<id>/` layout no v2
|
|
19
|
+
* writer populates. `appendTrace` went with the trace hook, its only
|
|
20
|
+
* producer. What remains is the live write surface.
|
|
17
21
|
*
|
|
18
22
|
* @module lib/signals
|
|
19
23
|
*/
|
|
20
24
|
|
|
21
|
-
import { read } from './read.js';
|
|
22
25
|
import * as schema from './schema.js';
|
|
23
|
-
import {
|
|
24
|
-
import { appendSignal, appendTrace, forEachLine } from './write.js';
|
|
26
|
+
import { appendSignal, forEachLine } from './write.js';
|
|
25
27
|
|
|
26
|
-
export { appendSignal,
|
|
28
|
+
export { appendSignal, forEachLine, schema };
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Signals schema — single source of truth for event-kind constants and
|
|
3
3
|
* shape guards (Epic #1181 / Story #1438 / Task #1458).
|
|
4
4
|
*
|
|
5
|
-
* Both writers (`lib/observability/signals-writer.js` callers) and
|
|
6
|
-
*
|
|
7
|
-
* import their field names from this module so the
|
|
8
|
-
* stays under one schema.
|
|
5
|
+
* Both writers (`lib/observability/signals-writer.js` callers) and readers
|
|
6
|
+
* (its `forEachLine` / `forEachSignalStreamLine` walkers, and the retro's
|
|
7
|
+
* gather-signals phase) import their field names from this module so the
|
|
8
|
+
* on-disk NDJSON shape stays under one schema.
|
|
9
9
|
*
|
|
10
10
|
* Pure module: importing has **no I/O side effects**. The exported
|
|
11
11
|
* `EVENT_KINDS` object is `Object.freeze`'d so consumers can use it as a
|
|
@@ -14,30 +14,25 @@
|
|
|
14
14
|
* ## Event-kind enumeration
|
|
15
15
|
*
|
|
16
16
|
* The set below is the audited union of `kind:` literals emitted via
|
|
17
|
-
* `signals-writer.appendSignal`
|
|
18
|
-
*
|
|
17
|
+
* `signals-writer.appendSignal` across the codebase as of Epic #1181
|
|
18
|
+
* (audit-snapshot 2026-05-11):
|
|
19
19
|
*
|
|
20
20
|
* Signals-writer `appendSignal` call sites:
|
|
21
21
|
* - `friction` — quality-gate / runtime friction (diagnose-friction.js,
|
|
22
22
|
* lib/gates/friction.js)
|
|
23
23
|
* - `acceptance-eval` — acceptance-eval.js per-criterion terminus signal.
|
|
24
|
-
* - `wave-start` / `wave-complete` — wave-window anchors
|
|
25
|
-
*
|
|
26
|
-
* - `wave-end` — span-tree pairing anchor (retained for span-tree)
|
|
24
|
+
* - `wave-start` / `wave-complete` — wave-window anchors.
|
|
25
|
+
* - `wave-end` — wave-window pairing anchor.
|
|
27
26
|
* - `state-transition` — notification-derived window anchor.
|
|
28
27
|
*
|
|
29
28
|
* The `dispatched` kind was deleted in the Epic #4406 signal-contract
|
|
30
29
|
* cutover — it had no live emitter and no consumer.
|
|
31
30
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* emitted by `lib/signals/detectors/*`; `hotspot`, `churn`, `idle` remain
|
|
38
|
-
* pinned in the enum for the aggregator's kind-count rollup even though no
|
|
39
|
-
* live detector emits them):
|
|
40
|
-
* - `hotspot`, `rework`, `churn`, `idle`, `retry`
|
|
31
|
+
* Producerless kinds, pinned in the enum so a re-introduction needs no schema
|
|
32
|
+
* bump. Story #5003 deleted the last emitters of `trace` (the tool-trace hook
|
|
33
|
+
* and its `appendTrace` writer limb) and of `rework` / `retry` (the detector
|
|
34
|
+
* pair the hook fed); `hotspot`, `churn`, `idle` never had one:
|
|
35
|
+
* - `trace`, `hotspot`, `rework`, `churn`, `idle`, `retry`
|
|
41
36
|
*
|
|
42
37
|
* ## Common envelope (canonical — Epic #4406 / Story #4413)
|
|
43
38
|
*
|
|
@@ -68,18 +63,18 @@ import { isPositiveInt } from './detectors/common.js';
|
|
|
68
63
|
|
|
69
64
|
/**
|
|
70
65
|
* Frozen enumeration of every event kind currently emitted by
|
|
71
|
-
* `signals-writer.appendSignal
|
|
72
|
-
*
|
|
66
|
+
* `signals-writer.appendSignal`, plus the producerless kinds a future Story
|
|
67
|
+
* may re-introduce an emitter for.
|
|
73
68
|
*
|
|
74
69
|
* Field names are kebab-cased to match the on-disk literals.
|
|
75
70
|
*/
|
|
76
71
|
export const EVENT_KINDS = Object.freeze({
|
|
77
72
|
FRICTION: 'friction',
|
|
78
73
|
TRACE: 'trace',
|
|
79
|
-
// Wave-window forensics signals
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
74
|
+
// Wave-window forensics signals. Story #4545 deleted the `waveParallelism`
|
|
75
|
+
// consumer (perf-aggregator) with the execution-analysis surface, and
|
|
76
|
+
// Story #5003 deleted the span-tree that paired them; the kinds stay
|
|
77
|
+
// pinned so a re-introduced window reader needs no schema bump.
|
|
83
78
|
WAVE_START: 'wave-start',
|
|
84
79
|
WAVE_END: 'wave-end',
|
|
85
80
|
WAVE_COMPLETE: 'wave-complete',
|
|
@@ -156,7 +151,7 @@ function isTimestamp(v) {
|
|
|
156
151
|
* recognised `kind`. Canonical keys only — the legacy `timestamp` / `epic`
|
|
157
152
|
* aliases were deleted in the Epic #4406 cutover.
|
|
158
153
|
*
|
|
159
|
-
* Returns true when the envelope is well-formed. Used by
|
|
154
|
+
* Returns true when the envelope is well-formed. Used by the stream readers
|
|
160
155
|
* to discard malformed lines before yielding them to the consumer.
|
|
161
156
|
*
|
|
162
157
|
* @param {unknown} evt
|