@encore-os/eos-spec 0.3.1
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/LICENSE +17 -0
- package/bin/check-dist-fresh.js +51 -0
- package/bin/eos-spec.js +7 -0
- package/dist/commands/ac-backfill.d.ts +31 -0
- package/dist/commands/ac-backfill.js +198 -0
- package/dist/commands/ac-backfill.js.map +1 -0
- package/dist/commands/archive.d.ts +2 -0
- package/dist/commands/archive.js +101 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/audit.d.ts +30 -0
- package/dist/commands/audit.js +107 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/backfill.d.ts +41 -0
- package/dist/commands/backfill.js +239 -0
- package/dist/commands/backfill.js.map +1 -0
- package/dist/commands/checkArtifacts.d.ts +2 -0
- package/dist/commands/checkArtifacts.js +48 -0
- package/dist/commands/checkArtifacts.js.map +1 -0
- package/dist/commands/checkPlacement.d.ts +2 -0
- package/dist/commands/checkPlacement.js +31 -0
- package/dist/commands/checkPlacement.js.map +1 -0
- package/dist/commands/claim.d.ts +4 -0
- package/dist/commands/claim.js +46 -0
- package/dist/commands/claim.js.map +1 -0
- package/dist/commands/complete.d.ts +2 -0
- package/dist/commands/complete.js +138 -0
- package/dist/commands/complete.js.map +1 -0
- package/dist/commands/conformance.d.ts +23 -0
- package/dist/commands/conformance.js +124 -0
- package/dist/commands/conformance.js.map +1 -0
- package/dist/commands/create.d.ts +6 -0
- package/dist/commands/create.js +277 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/deferred.d.ts +2 -0
- package/dist/commands/deferred.js +85 -0
- package/dist/commands/deferred.js.map +1 -0
- package/dist/commands/delta.d.ts +17 -0
- package/dist/commands/delta.js +292 -0
- package/dist/commands/delta.js.map +1 -0
- package/dist/commands/freshness.d.ts +13 -0
- package/dist/commands/freshness.js +72 -0
- package/dist/commands/freshness.js.map +1 -0
- package/dist/commands/funnel.d.ts +2 -0
- package/dist/commands/funnel.js +61 -0
- package/dist/commands/funnel.js.map +1 -0
- package/dist/commands/gen-tests.d.ts +2 -0
- package/dist/commands/gen-tests.js +53 -0
- package/dist/commands/gen-tests.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +64 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +9 -0
- package/dist/commands/list.js +61 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/metrics.d.ts +2 -0
- package/dist/commands/metrics.js +66 -0
- package/dist/commands/metrics.js.map +1 -0
- package/dist/commands/next.d.ts +48 -0
- package/dist/commands/next.js +344 -0
- package/dist/commands/next.js.map +1 -0
- package/dist/commands/precedent.d.ts +66 -0
- package/dist/commands/precedent.js +343 -0
- package/dist/commands/precedent.js.map +1 -0
- package/dist/commands/ready.d.ts +28 -0
- package/dist/commands/ready.js +147 -0
- package/dist/commands/ready.js.map +1 -0
- package/dist/commands/reconcile.d.ts +44 -0
- package/dist/commands/reconcile.js +187 -0
- package/dist/commands/reconcile.js.map +1 -0
- package/dist/commands/rollup.d.ts +2 -0
- package/dist/commands/rollup.js +43 -0
- package/dist/commands/rollup.js.map +1 -0
- package/dist/commands/scorecard.d.ts +11 -0
- package/dist/commands/scorecard.js +55 -0
- package/dist/commands/scorecard.js.map +1 -0
- package/dist/commands/signoff.d.ts +2 -0
- package/dist/commands/signoff.js +114 -0
- package/dist/commands/signoff.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +100 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/tasks.d.ts +13 -0
- package/dist/commands/tasks.js +81 -0
- package/dist/commands/tasks.js.map +1 -0
- package/dist/commands/validate.d.ts +16 -0
- package/dist/commands/validate.js +89 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/commands/verify-acs.d.ts +15 -0
- package/dist/commands/verify-acs.js +196 -0
- package/dist/commands/verify-acs.js.map +1 -0
- package/dist/commands/verifyRollout.d.ts +2 -0
- package/dist/commands/verifyRollout.js +125 -0
- package/dist/commands/verifyRollout.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/ac-draft-prompt.d.ts +18 -0
- package/dist/lib/ac-draft-prompt.js +38 -0
- package/dist/lib/ac-draft-prompt.js.map +1 -0
- package/dist/lib/ac-response-parser.d.ts +18 -0
- package/dist/lib/ac-response-parser.js +57 -0
- package/dist/lib/ac-response-parser.js.map +1 -0
- package/dist/lib/ai-call.d.ts +13 -0
- package/dist/lib/ai-call.js +17 -0
- package/dist/lib/ai-call.js.map +1 -0
- package/dist/lib/archive.d.ts +21 -0
- package/dist/lib/archive.js +136 -0
- package/dist/lib/archive.js.map +1 -0
- package/dist/lib/artifact-integrity.d.ts +5 -0
- package/dist/lib/artifact-integrity.js +65 -0
- package/dist/lib/artifact-integrity.js.map +1 -0
- package/dist/lib/backfill-queue.d.ts +11 -0
- package/dist/lib/backfill-queue.js +14 -0
- package/dist/lib/backfill-queue.js.map +1 -0
- package/dist/lib/blockers.d.ts +66 -0
- package/dist/lib/blockers.js +103 -0
- package/dist/lib/blockers.js.map +1 -0
- package/dist/lib/claims.d.ts +39 -0
- package/dist/lib/claims.js +133 -0
- package/dist/lib/claims.js.map +1 -0
- package/dist/lib/classify-debt.d.ts +17 -0
- package/dist/lib/classify-debt.js +13 -0
- package/dist/lib/classify-debt.js.map +1 -0
- package/dist/lib/code-evidence.d.ts +35 -0
- package/dist/lib/code-evidence.js +171 -0
- package/dist/lib/code-evidence.js.map +1 -0
- package/dist/lib/coherence.d.ts +14 -0
- package/dist/lib/coherence.js +188 -0
- package/dist/lib/coherence.js.map +1 -0
- package/dist/lib/completion-gate.d.ts +17 -0
- package/dist/lib/completion-gate.js +28 -0
- package/dist/lib/completion-gate.js.map +1 -0
- package/dist/lib/compliance-signoff.d.ts +39 -0
- package/dist/lib/compliance-signoff.js +81 -0
- package/dist/lib/compliance-signoff.js.map +1 -0
- package/dist/lib/config.d.ts +63 -0
- package/dist/lib/config.js +40 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/cost.d.ts +12 -0
- package/dist/lib/cost.js +12 -0
- package/dist/lib/cost.js.map +1 -0
- package/dist/lib/debt-dashboard.d.ts +25 -0
- package/dist/lib/debt-dashboard.js +36 -0
- package/dist/lib/debt-dashboard.js.map +1 -0
- package/dist/lib/embedding-store.d.ts +103 -0
- package/dist/lib/embedding-store.js +133 -0
- package/dist/lib/embedding-store.js.map +1 -0
- package/dist/lib/embeddings-source.d.ts +36 -0
- package/dist/lib/embeddings-source.js +62 -0
- package/dist/lib/embeddings-source.js.map +1 -0
- package/dist/lib/enforcement-allowlist.d.ts +12 -0
- package/dist/lib/enforcement-allowlist.js +25 -0
- package/dist/lib/enforcement-allowlist.js.map +1 -0
- package/dist/lib/events.d.ts +47 -0
- package/dist/lib/events.js +75 -0
- package/dist/lib/events.js.map +1 -0
- package/dist/lib/exec.d.ts +8 -0
- package/dist/lib/exec.js +25 -0
- package/dist/lib/exec.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +112 -0
- package/dist/lib/frontmatter.js +261 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/funnel.d.ts +22 -0
- package/dist/lib/funnel.js +41 -0
- package/dist/lib/funnel.js.map +1 -0
- package/dist/lib/git-mtime.d.ts +4 -0
- package/dist/lib/git-mtime.js +23 -0
- package/dist/lib/git-mtime.js.map +1 -0
- package/dist/lib/metrics.d.ts +42 -0
- package/dist/lib/metrics.js +92 -0
- package/dist/lib/metrics.js.map +1 -0
- package/dist/lib/migration-collision.d.ts +20 -0
- package/dist/lib/migration-collision.js +61 -0
- package/dist/lib/migration-collision.js.map +1 -0
- package/dist/lib/next-action.d.ts +44 -0
- package/dist/lib/next-action.js +138 -0
- package/dist/lib/next-action.js.map +1 -0
- package/dist/lib/ollama-embed.d.ts +21 -0
- package/dist/lib/ollama-embed.js +65 -0
- package/dist/lib/ollama-embed.js.map +1 -0
- package/dist/lib/output.d.ts +35 -0
- package/dist/lib/output.js +46 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/pipeline-config.d.ts +20 -0
- package/dist/lib/pipeline-config.js +208 -0
- package/dist/lib/pipeline-config.js.map +1 -0
- package/dist/lib/pipeline-map.d.ts +68 -0
- package/dist/lib/pipeline-map.js +256 -0
- package/dist/lib/pipeline-map.js.map +1 -0
- package/dist/lib/pipeline-schema.d.ts +48 -0
- package/dist/lib/pipeline-schema.js +22 -0
- package/dist/lib/pipeline-schema.js.map +1 -0
- package/dist/lib/placement.d.ts +6 -0
- package/dist/lib/placement.js +40 -0
- package/dist/lib/placement.js.map +1 -0
- package/dist/lib/precedent-search.d.ts +18 -0
- package/dist/lib/precedent-search.js +160 -0
- package/dist/lib/precedent-search.js.map +1 -0
- package/dist/lib/predicate-registry.d.ts +6 -0
- package/dist/lib/predicate-registry.js +40 -0
- package/dist/lib/predicate-registry.js.map +1 -0
- package/dist/lib/recall-cases.d.ts +15 -0
- package/dist/lib/recall-cases.js +39 -0
- package/dist/lib/recall-cases.js.map +1 -0
- package/dist/lib/repo.d.ts +20 -0
- package/dist/lib/repo.js +82 -0
- package/dist/lib/repo.js.map +1 -0
- package/dist/lib/requirements.d.ts +91 -0
- package/dist/lib/requirements.js +179 -0
- package/dist/lib/requirements.js.map +1 -0
- package/dist/lib/rollup.d.ts +24 -0
- package/dist/lib/rollup.js +34 -0
- package/dist/lib/rollup.js.map +1 -0
- package/dist/lib/scorecard-run.d.ts +6 -0
- package/dist/lib/scorecard-run.js +48 -0
- package/dist/lib/scorecard-run.js.map +1 -0
- package/dist/lib/scorecard.d.ts +69 -0
- package/dist/lib/scorecard.js +113 -0
- package/dist/lib/scorecard.js.map +1 -0
- package/dist/lib/semantic-search.d.ts +36 -0
- package/dist/lib/semantic-search.js +56 -0
- package/dist/lib/semantic-search.js.map +1 -0
- package/dist/lib/session-state.d.ts +8 -0
- package/dist/lib/session-state.js +25 -0
- package/dist/lib/session-state.js.map +1 -0
- package/dist/lib/spec-debt.d.ts +8 -0
- package/dist/lib/spec-debt.js +17 -0
- package/dist/lib/spec-debt.js.map +1 -0
- package/dist/lib/spec-pure.d.ts +59 -0
- package/dist/lib/spec-pure.js +215 -0
- package/dist/lib/spec-pure.js.map +1 -0
- package/dist/lib/specs.d.ts +148 -0
- package/dist/lib/specs.js +373 -0
- package/dist/lib/specs.js.map +1 -0
- package/dist/lib/test-generator.d.ts +5 -0
- package/dist/lib/test-generator.js +72 -0
- package/dist/lib/test-generator.js.map +1 -0
- package/dist/lib/user-stories.d.ts +6 -0
- package/dist/lib/user-stories.js +56 -0
- package/dist/lib/user-stories.js.map +1 -0
- package/dist/lib/verifies-scan.d.ts +9 -0
- package/dist/lib/verifies-scan.js +73 -0
- package/dist/lib/verifies-scan.js.map +1 -0
- package/dist/lib/verify-core.d.ts +23 -0
- package/dist/lib/verify-core.js +48 -0
- package/dist/lib/verify-core.js.map +1 -0
- package/dist/lib/with-event-log.d.ts +12 -0
- package/dist/lib/with-event-log.js +65 -0
- package/dist/lib/with-event-log.js.map +1 -0
- package/dist/lib/wizard-specs.d.ts +17 -0
- package/dist/lib/wizard-specs.js +34 -0
- package/dist/lib/wizard-specs.js.map +1 -0
- package/dist/pure.d.ts +19 -0
- package/dist/pure.js +31 -0
- package/dist/pure.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { SpecFile } from './specs.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extract a deduped, upper-cased list of spec ids from a possibly-dirty
|
|
4
|
+
* frontmatter value. Tolerates `null`, a scalar, or a list whose entries are
|
|
5
|
+
* polluted with prose fragments and status emoji (the real shape of today's
|
|
6
|
+
* `dependencies:` data). Anything that is not a spec-id token is dropped.
|
|
7
|
+
*/
|
|
8
|
+
export declare function collectSpecIds(value: unknown): string[];
|
|
9
|
+
/**
|
|
10
|
+
* True when a status string indicates the spec is DONE (so it clears a blocker).
|
|
11
|
+
*
|
|
12
|
+
* Deliberately conservative and ANCHORED at the start: the repo's "done" signal is
|
|
13
|
+
* a leading ✅ or a leading `complete`/`implemented` word. Anchoring avoids clearing
|
|
14
|
+
* a blocker on statuses that merely CONTAIN "complete" partway through — real data
|
|
15
|
+
* includes `~95% Complete`, `📋 Analysis Complete`, `🚧 In Progress (60% Complete)`,
|
|
16
|
+
* none of which are done. For a blocker predicate a false-positive (wrongly clearing)
|
|
17
|
+
* is worse than a false-negative; the latter is softened because `next` still surfaces
|
|
18
|
+
* a blocked spec with an annotated note. `implemented` is included to match the
|
|
19
|
+
* completion model in archive.ts / coherence.ts.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isCompleteStatus(status: string | undefined): boolean;
|
|
22
|
+
/** True when a spec's textual status indicates completion. */
|
|
23
|
+
export declare function isComplete(spec: SpecFile): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* True when a spec is in a TERMINAL org-lifecycle state and should be dropped from the
|
|
26
|
+
* navigator's core-pick candidate pool. Parallel to `isCompleteStatus` (also start-anchored)
|
|
27
|
+
* but for the deferred/archived/blocked/superseded family.
|
|
28
|
+
*
|
|
29
|
+
* Two terminal signals:
|
|
30
|
+
* - `status` starts with deferred/archived/blocked/superseded (case-insensitive,
|
|
31
|
+
* start-anchored — mirrors isCompleteStatus's anti-false-positive shape so a status that
|
|
32
|
+
* merely CONTAINS a terminal word partway through, e.g. "needs unblocking", is not terminal).
|
|
33
|
+
* - a `superseded_by` value is present (the spec points at its replacement).
|
|
34
|
+
*
|
|
35
|
+
* ⚠ Keys on `status` (the org-lifecycle axis) + `superseded_by`, NEVER on `pipeline_status`:
|
|
36
|
+
* HR-24 carries `status: archived` AND `pipeline_status: stub` simultaneously, so filtering on
|
|
37
|
+
* pipeline_status would leave every archived-but-stub spec in the candidate pool.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isTerminalStatus(status: string | undefined, supersededBy?: string): boolean;
|
|
40
|
+
/** True when a spec's org-lifecycle status (or a superseded_by pointer) marks it terminal. */
|
|
41
|
+
export declare function isTerminal(spec: SpecFile): boolean;
|
|
42
|
+
export type BlockerState = 'complete' | 'open' | 'unknown';
|
|
43
|
+
export type BlockerRef = {
|
|
44
|
+
/** Upper-cased blocker spec id. */
|
|
45
|
+
id: string;
|
|
46
|
+
/** complete → cleared; open → still in progress; unknown → id resolves to no spec on disk. */
|
|
47
|
+
state: BlockerState;
|
|
48
|
+
};
|
|
49
|
+
export type Readiness = {
|
|
50
|
+
spec: SpecFile;
|
|
51
|
+
/** True when the spec has no blockers, or every blocker is complete. */
|
|
52
|
+
ready: boolean;
|
|
53
|
+
blockers: BlockerRef[];
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Classify each candidate as ready or blocked by resolving its `blocked_by`
|
|
57
|
+
* ids against the full corpus (cross-core: a blocker may live in any core).
|
|
58
|
+
*/
|
|
59
|
+
export declare function computeReadiness(candidates: SpecFile[], corpus: SpecFile[]): Readiness[];
|
|
60
|
+
/**
|
|
61
|
+
* Order candidates ready-first while preserving input order within each group
|
|
62
|
+
* (Array.prototype.sort is stable on Node 24). Feeds `next`'s candidate pick.
|
|
63
|
+
*/
|
|
64
|
+
export declare function orderByReadiness(candidates: SpecFile[], corpus: SpecFile[]): Readiness[];
|
|
65
|
+
/** Blockers that are not yet cleared (open or unknown) — the ones worth surfacing in a note. */
|
|
66
|
+
export declare function unclearedBlockers(readiness: Readiness): BlockerRef[];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-spec blocker awareness — the `bd ready` borrow from beads.
|
|
3
|
+
*
|
|
4
|
+
* Design (decided 2026-06-02):
|
|
5
|
+
* - `blocked_by` frontmatter is the canonical machine field. The legacy
|
|
6
|
+
* `dependencies` field stays human prose and is only read by the proposer.
|
|
7
|
+
* - A blocker is "cleared" only when its spec reaches Complete (binary,
|
|
8
|
+
* matches beads' closed/open model). No transitive recursion: readiness
|
|
9
|
+
* looks one level deep at each blocker's *status*, so dependency cycles
|
|
10
|
+
* can never loop — a cycle simply leaves both specs blocked until one
|
|
11
|
+
* actually completes.
|
|
12
|
+
* - An unknown/unresolvable blocker id is treated as blocking and flagged,
|
|
13
|
+
* never silently cleared (accuracy over convenience).
|
|
14
|
+
*/
|
|
15
|
+
/** Matches an Encore spec id (e.g. `PF-96`, `HR-13-EN-08`, `CL-15-E2`, `LO-29`). */
|
|
16
|
+
const SPEC_ID_RE = /\b[A-Z]{2,3}-(?:[A-Z]{1,4}-)?\d+(?:\.\d+)?(?:-(?:EN-\d+|E\d+|P\d+(?:\.\d+)?(?:-EN-\d+)?))?\b/g;
|
|
17
|
+
/**
|
|
18
|
+
* Extract a deduped, upper-cased list of spec ids from a possibly-dirty
|
|
19
|
+
* frontmatter value. Tolerates `null`, a scalar, or a list whose entries are
|
|
20
|
+
* polluted with prose fragments and status emoji (the real shape of today's
|
|
21
|
+
* `dependencies:` data). Anything that is not a spec-id token is dropped.
|
|
22
|
+
*/
|
|
23
|
+
export function collectSpecIds(value) {
|
|
24
|
+
if (value == null)
|
|
25
|
+
return [];
|
|
26
|
+
const text = Array.isArray(value) ? value.map((v) => String(v)).join('\n') : String(value);
|
|
27
|
+
const ids = new Set();
|
|
28
|
+
for (const m of text.matchAll(SPEC_ID_RE))
|
|
29
|
+
ids.add(m[0].toUpperCase());
|
|
30
|
+
return [...ids];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* True when a status string indicates the spec is DONE (so it clears a blocker).
|
|
34
|
+
*
|
|
35
|
+
* Deliberately conservative and ANCHORED at the start: the repo's "done" signal is
|
|
36
|
+
* a leading ✅ or a leading `complete`/`implemented` word. Anchoring avoids clearing
|
|
37
|
+
* a blocker on statuses that merely CONTAIN "complete" partway through — real data
|
|
38
|
+
* includes `~95% Complete`, `📋 Analysis Complete`, `🚧 In Progress (60% Complete)`,
|
|
39
|
+
* none of which are done. For a blocker predicate a false-positive (wrongly clearing)
|
|
40
|
+
* is worse than a false-negative; the latter is softened because `next` still surfaces
|
|
41
|
+
* a blocked spec with an annotated note. `implemented` is included to match the
|
|
42
|
+
* completion model in archive.ts / coherence.ts.
|
|
43
|
+
*/
|
|
44
|
+
export function isCompleteStatus(status) {
|
|
45
|
+
return Boolean(status && /^(?:✅|complete|implemented)/i.test(status.trim()));
|
|
46
|
+
}
|
|
47
|
+
/** True when a spec's textual status indicates completion. */
|
|
48
|
+
export function isComplete(spec) {
|
|
49
|
+
return isCompleteStatus(spec.status);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* True when a spec is in a TERMINAL org-lifecycle state and should be dropped from the
|
|
53
|
+
* navigator's core-pick candidate pool. Parallel to `isCompleteStatus` (also start-anchored)
|
|
54
|
+
* but for the deferred/archived/blocked/superseded family.
|
|
55
|
+
*
|
|
56
|
+
* Two terminal signals:
|
|
57
|
+
* - `status` starts with deferred/archived/blocked/superseded (case-insensitive,
|
|
58
|
+
* start-anchored — mirrors isCompleteStatus's anti-false-positive shape so a status that
|
|
59
|
+
* merely CONTAINS a terminal word partway through, e.g. "needs unblocking", is not terminal).
|
|
60
|
+
* - a `superseded_by` value is present (the spec points at its replacement).
|
|
61
|
+
*
|
|
62
|
+
* ⚠ Keys on `status` (the org-lifecycle axis) + `superseded_by`, NEVER on `pipeline_status`:
|
|
63
|
+
* HR-24 carries `status: archived` AND `pipeline_status: stub` simultaneously, so filtering on
|
|
64
|
+
* pipeline_status would leave every archived-but-stub spec in the candidate pool.
|
|
65
|
+
*/
|
|
66
|
+
export function isTerminalStatus(status, supersededBy) {
|
|
67
|
+
if (supersededBy && supersededBy.trim() !== '')
|
|
68
|
+
return true;
|
|
69
|
+
return Boolean(status && /^(?:deferred|archived|blocked|superseded)/i.test(status.trim()));
|
|
70
|
+
}
|
|
71
|
+
/** True when a spec's org-lifecycle status (or a superseded_by pointer) marks it terminal. */
|
|
72
|
+
export function isTerminal(spec) {
|
|
73
|
+
return isTerminalStatus(spec.status, spec.supersededBy);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Classify each candidate as ready or blocked by resolving its `blocked_by`
|
|
77
|
+
* ids against the full corpus (cross-core: a blocker may live in any core).
|
|
78
|
+
*/
|
|
79
|
+
export function computeReadiness(candidates, corpus) {
|
|
80
|
+
const byId = new Map(corpus.map((s) => [s.id.toUpperCase(), s]));
|
|
81
|
+
return candidates.map((spec) => {
|
|
82
|
+
const blockers = spec.signals.blockedBy.map((rawId) => {
|
|
83
|
+
const id = rawId.toUpperCase();
|
|
84
|
+
const target = byId.get(id);
|
|
85
|
+
const state = !target ? 'unknown' : isComplete(target) ? 'complete' : 'open';
|
|
86
|
+
return { id, state };
|
|
87
|
+
});
|
|
88
|
+
const ready = blockers.every((b) => b.state === 'complete');
|
|
89
|
+
return { spec, ready, blockers };
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Order candidates ready-first while preserving input order within each group
|
|
94
|
+
* (Array.prototype.sort is stable on Node 24). Feeds `next`'s candidate pick.
|
|
95
|
+
*/
|
|
96
|
+
export function orderByReadiness(candidates, corpus) {
|
|
97
|
+
return [...computeReadiness(candidates, corpus)].sort((a, b) => Number(b.ready) - Number(a.ready));
|
|
98
|
+
}
|
|
99
|
+
/** Blockers that are not yet cleared (open or unknown) — the ones worth surfacing in a note. */
|
|
100
|
+
export function unclearedBlockers(readiness) {
|
|
101
|
+
return readiness.blockers.filter((b) => b.state !== 'complete');
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=blockers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blockers.js","sourceRoot":"","sources":["../../src/lib/blockers.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,oFAAoF;AACpF,MAAM,UAAU,GAAG,+FAA+F,CAAC;AAEnH;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3F,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAA0B;IACzD,OAAO,OAAO,CAAC,MAAM,IAAI,8BAA8B,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,UAAU,CAAC,IAAc;IACvC,OAAO,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAA0B,EAAE,YAAqB;IAChF,IAAI,YAAY,IAAI,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC5D,OAAO,OAAO,CAAC,MAAM,IAAI,4CAA4C,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7F,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,UAAU,CAAC,IAAc;IACvC,OAAO,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;AAC1D,CAAC;AAkBD;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAsB,EAAE,MAAkB;IACzE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACjE,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAC7B,MAAM,QAAQ,GAAiB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAClE,MAAM,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC5B,MAAM,KAAK,GAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC;YAC3F,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC;QAC5D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAsB,EAAE,MAAkB;IACzE,OAAO,CAAC,GAAG,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AACrG,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,iBAAiB,CAAC,SAAoB;IACpD,OAAO,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** One in-flight reservation of a spec by a parallel worktree agent (C1). */
|
|
2
|
+
export type Claim = {
|
|
3
|
+
id: string;
|
|
4
|
+
claimedBy: string;
|
|
5
|
+
/** ISO-8601 timestamp the claim was taken. */
|
|
6
|
+
claimedAt: string;
|
|
7
|
+
ttlMinutes: number;
|
|
8
|
+
};
|
|
9
|
+
export type ClaimStore = {
|
|
10
|
+
claims: Claim[];
|
|
11
|
+
};
|
|
12
|
+
/** Default lease window when --ttl is not given. */
|
|
13
|
+
export declare const DEFAULT_TTL_MINUTES = 90;
|
|
14
|
+
/** Absolute path of the claim store (C1). */
|
|
15
|
+
export declare function claimsPath(repoRoot: string): string;
|
|
16
|
+
/** Read the store; `{claims:[]}` when the file is absent or unparseable (never throws). */
|
|
17
|
+
export declare function readClaims(repoRoot: string): ClaimStore;
|
|
18
|
+
/** Atomic write: serialize to a unique temp file in the same dir, then rename over the target. */
|
|
19
|
+
export declare function writeClaims(repoRoot: string, store: ClaimStore): void;
|
|
20
|
+
/** Claims still within their TTL window relative to `nowMs`. */
|
|
21
|
+
export declare function activeClaims(store: ClaimStore, nowMs: number): Claim[];
|
|
22
|
+
/**
|
|
23
|
+
* Exclusive lockfile mutex around a read-modify-write. The lock is created with
|
|
24
|
+
* the `wx` flag (fails if it already exists); on contention we retry with a
|
|
25
|
+
* short bounded backoff, then break a clearly-stale lock so a crashed agent
|
|
26
|
+
* cannot wedge the store forever. Always released in `finally`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function withClaimLock<T>(repoRoot: string, fn: () => T): T;
|
|
29
|
+
/**
|
|
30
|
+
* Reserve a spec id (upper-cased). Lock-guarded read-modify-write: drops any
|
|
31
|
+
* existing claim for the same id (re-claim refreshes the lease), appends the
|
|
32
|
+
* new one, and persists atomically. Returns the stored claim.
|
|
33
|
+
*/
|
|
34
|
+
export declare function addClaim(repoRoot: string, id: string, opts: {
|
|
35
|
+
by: string;
|
|
36
|
+
ttlMinutes: number;
|
|
37
|
+
}): Claim;
|
|
38
|
+
/** Release a claim by id (no-op if absent). Lock-guarded. */
|
|
39
|
+
export declare function releaseClaim(repoRoot: string, id: string): void;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, join } from 'node:path';
|
|
3
|
+
import { runScript } from './exec.js';
|
|
4
|
+
const STORE_FILENAME = 'eos-spec-claims.json';
|
|
5
|
+
const LOCK_FILENAME = 'eos-spec-claims.lock';
|
|
6
|
+
/** Default lease window when --ttl is not given. */
|
|
7
|
+
export const DEFAULT_TTL_MINUTES = 90;
|
|
8
|
+
/**
|
|
9
|
+
* Directory shared across every worktree of one repo: the common git dir. A
|
|
10
|
+
* worktree's `.git` is a FILE, so we MUST ask git, never path-join `.git`.
|
|
11
|
+
* Falls back to `<repoRoot>/.git` only when the git call fails (e.g. a temp
|
|
12
|
+
* dir with no repo), so unit tests stay deterministic.
|
|
13
|
+
*/
|
|
14
|
+
function commonGitDir(repoRoot) {
|
|
15
|
+
const r = runScript(['git', 'rev-parse', '--git-common-dir'], repoRoot);
|
|
16
|
+
const out = r.stdout.trim();
|
|
17
|
+
if (r.exitCode === 0 && out)
|
|
18
|
+
return isAbsolute(out) ? out : join(repoRoot, out);
|
|
19
|
+
return join(repoRoot, '.git');
|
|
20
|
+
}
|
|
21
|
+
/** Absolute path of the claim store (C1). */
|
|
22
|
+
export function claimsPath(repoRoot) {
|
|
23
|
+
return join(commonGitDir(repoRoot), STORE_FILENAME);
|
|
24
|
+
}
|
|
25
|
+
function lockPath(repoRoot) {
|
|
26
|
+
return join(commonGitDir(repoRoot), LOCK_FILENAME);
|
|
27
|
+
}
|
|
28
|
+
/** Read the store; `{claims:[]}` when the file is absent or unparseable (never throws). */
|
|
29
|
+
export function readClaims(repoRoot) {
|
|
30
|
+
let raw;
|
|
31
|
+
try {
|
|
32
|
+
raw = readFileSync(claimsPath(repoRoot), 'utf8');
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { claims: [] };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
const claims = parsed?.claims;
|
|
40
|
+
return Array.isArray(claims) ? { claims } : { claims: [] };
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { claims: [] };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Atomic write: serialize to a unique temp file in the same dir, then rename over the target. */
|
|
47
|
+
export function writeClaims(repoRoot, store) {
|
|
48
|
+
const target = claimsPath(repoRoot);
|
|
49
|
+
mkdirSync(commonGitDir(repoRoot), { recursive: true });
|
|
50
|
+
const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
51
|
+
writeFileSync(tmp, `${JSON.stringify(store, null, 2)}\n`, 'utf8');
|
|
52
|
+
renameSync(tmp, target);
|
|
53
|
+
}
|
|
54
|
+
/** Claims still within their TTL window relative to `nowMs`. */
|
|
55
|
+
export function activeClaims(store, nowMs) {
|
|
56
|
+
return store.claims.filter((c) => {
|
|
57
|
+
const startMs = Date.parse(c.claimedAt);
|
|
58
|
+
if (Number.isNaN(startMs))
|
|
59
|
+
return false; // unparseable timestamp → treat as stale
|
|
60
|
+
return startMs + c.ttlMinutes * 60_000 >= nowMs;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Exclusive lockfile mutex around a read-modify-write. The lock is created with
|
|
65
|
+
* the `wx` flag (fails if it already exists); on contention we retry with a
|
|
66
|
+
* short bounded backoff, then break a clearly-stale lock so a crashed agent
|
|
67
|
+
* cannot wedge the store forever. Always released in `finally`.
|
|
68
|
+
*/
|
|
69
|
+
export function withClaimLock(repoRoot, fn) {
|
|
70
|
+
const lock = lockPath(repoRoot);
|
|
71
|
+
mkdirSync(commonGitDir(repoRoot), { recursive: true });
|
|
72
|
+
const maxAttempts = 50;
|
|
73
|
+
const backoffMs = 20;
|
|
74
|
+
const staleLockMs = 30_000;
|
|
75
|
+
let acquired = false;
|
|
76
|
+
for (let attempt = 0; attempt < maxAttempts && !acquired; attempt++) {
|
|
77
|
+
try {
|
|
78
|
+
writeFileSync(lock, `${process.pid}@${new Date().toISOString()}\n`, { flag: 'wx' });
|
|
79
|
+
acquired = true;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Lock held — break it if it is clearly stale, else spin-wait briefly.
|
|
83
|
+
try {
|
|
84
|
+
const heldAt = Date.parse(readFileSync(lock, 'utf8').split('@')[1]?.trim() ?? '');
|
|
85
|
+
if (!Number.isNaN(heldAt) && Date.now() - heldAt > staleLockMs)
|
|
86
|
+
rmSync(lock, { force: true });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// unreadable lock — fall through to the busy-wait
|
|
90
|
+
}
|
|
91
|
+
const until = Date.now() + backoffMs;
|
|
92
|
+
while (Date.now() < until) {
|
|
93
|
+
/* bounded busy-wait — claim ops are sub-ms, so this never spins long */
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return fn();
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
if (acquired)
|
|
102
|
+
rmSync(lock, { force: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Reserve a spec id (upper-cased). Lock-guarded read-modify-write: drops any
|
|
107
|
+
* existing claim for the same id (re-claim refreshes the lease), appends the
|
|
108
|
+
* new one, and persists atomically. Returns the stored claim.
|
|
109
|
+
*/
|
|
110
|
+
export function addClaim(repoRoot, id, opts) {
|
|
111
|
+
const claim = {
|
|
112
|
+
id: id.toUpperCase(),
|
|
113
|
+
claimedBy: opts.by,
|
|
114
|
+
claimedAt: new Date().toISOString(),
|
|
115
|
+
ttlMinutes: opts.ttlMinutes,
|
|
116
|
+
};
|
|
117
|
+
return withClaimLock(repoRoot, () => {
|
|
118
|
+
const store = readClaims(repoRoot);
|
|
119
|
+
const claims = store.claims.filter((c) => c.id !== claim.id);
|
|
120
|
+
claims.push(claim);
|
|
121
|
+
writeClaims(repoRoot, { claims });
|
|
122
|
+
return claim;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** Release a claim by id (no-op if absent). Lock-guarded. */
|
|
126
|
+
export function releaseClaim(repoRoot, id) {
|
|
127
|
+
const upper = id.toUpperCase();
|
|
128
|
+
withClaimLock(repoRoot, () => {
|
|
129
|
+
const store = readClaims(repoRoot);
|
|
130
|
+
writeClaims(repoRoot, { claims: store.claims.filter((c) => c.id !== upper) });
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=claims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.js","sourceRoot":"","sources":["../../src/lib/claims.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type DebtTier = 'T0' | 'T1' | 'T2' | 'T3' | 'T4';
|
|
2
|
+
export interface ScorecardLite {
|
|
3
|
+
claimsComplete: boolean;
|
|
4
|
+
requirementsTotal: number;
|
|
5
|
+
requirementsVerified: number;
|
|
6
|
+
}
|
|
7
|
+
export interface CodeEvidence {
|
|
8
|
+
hasTestsPass: boolean;
|
|
9
|
+
hasCode: boolean;
|
|
10
|
+
hasScaffold?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface TierResult {
|
|
13
|
+
tier: DebtTier;
|
|
14
|
+
honestStatus: 'complete' | 'partial' | 'scaffolded' | 'draft';
|
|
15
|
+
action: 'keep' | 'relink' | 'downgrade';
|
|
16
|
+
}
|
|
17
|
+
export declare function classifyTier(s: ScorecardLite, e: CodeEvidence): TierResult;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function classifyTier(s, e) {
|
|
2
|
+
if (s.claimsComplete && s.requirementsVerified > 0) {
|
|
3
|
+
return { tier: 'T0', honestStatus: 'complete', action: 'keep' };
|
|
4
|
+
}
|
|
5
|
+
if (e.hasTestsPass)
|
|
6
|
+
return { tier: 'T1', honestStatus: 'complete', action: 'relink' };
|
|
7
|
+
if (e.hasCode)
|
|
8
|
+
return { tier: 'T2', honestStatus: 'partial', action: 'downgrade' };
|
|
9
|
+
if (e.hasScaffold)
|
|
10
|
+
return { tier: 'T3', honestStatus: 'scaffolded', action: 'downgrade' };
|
|
11
|
+
return { tier: 'T4', honestStatus: 'draft', action: 'downgrade' };
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=classify-debt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify-debt.js","sourceRoot":"","sources":["../../src/lib/classify-debt.ts"],"names":[],"mappings":"AAmBA,MAAM,UAAU,YAAY,CAAC,CAAgB,EAAE,CAAe;IAC5D,IAAI,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,oBAAoB,GAAG,CAAC,EAAE,CAAC;QACnD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,CAAC,YAAY;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACtF,IAAI,CAAC,CAAC,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACnF,IAAI,CAAC,CAAC,WAAW;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAC1F,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AACpE,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CodeEvidence } from './classify-debt.js';
|
|
2
|
+
import type { ScorecardRow } from './scorecard.js';
|
|
3
|
+
import type { SpecFile } from './specs.js';
|
|
4
|
+
/**
|
|
5
|
+
* Code-evidence probe result. Extends classify-debt's CodeEvidence (the exact shape classifyTier
|
|
6
|
+
* consumes) with a human-readable `reason` for the debt dashboard — composed here so the
|
|
7
|
+
* classify-debt interface stays a pure tier input and is never polluted with prose.
|
|
8
|
+
*/
|
|
9
|
+
export interface CodeEvidenceResult extends CodeEvidence {
|
|
10
|
+
/** Short justification of how hasCode / hasTestsPass / hasScaffold were decided. */
|
|
11
|
+
reason: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Extract candidate table/entity tokens (`<core>_<name>`) a spec declares. We read the Data Model
|
|
15
|
+
* `#### \`hr_xxx\`` headers and any `CREATE TABLE hr_xxx` / bare `hr_xxx` identifiers in the body.
|
|
16
|
+
* These tokens are the concrete strings we grep for in src + migrations to prove implementation.
|
|
17
|
+
* Conservative: when a spec declares no such token we return an empty set and the caller will NOT
|
|
18
|
+
* guess hasCode from the id alone unless the id itself is referenced in source.
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractTableTokens(specText: string, core: string): Set<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Derive code/test evidence for one spec, CONSERVATIVELY (prefer surfacing uncertainty over an
|
|
23
|
+
* aggressive downgrade). Signals:
|
|
24
|
+
* - hasTestsPass: TRUE iff row.verifiedUndeclared > 0 — a genuine `@verifies` backlink exists for
|
|
25
|
+
* this spec but points at an undeclared/typo'd id (tests exist, linkage broken → T1/relink). This
|
|
26
|
+
* is the ONLY legitimate T1/relink case: when row.requirementsVerified > 0 classifyTier already
|
|
27
|
+
* returns T0, so a real declared backlink never reaches here. An incidental id-mention in a
|
|
28
|
+
* `tests/` file (a comment, fixture string, unrelated reference) is NOT proof of a passing test —
|
|
29
|
+
* it is surfaced as an informational hint in the reason only, and never sets hasTestsPass.
|
|
30
|
+
* - hasCode: a migration or `src/cores/<core>/` source file mentions one of the spec's declared
|
|
31
|
+
* table tokens, or references the spec id directly. When no reliable token can be extracted and
|
|
32
|
+
* the id is unreferenced, hasCode stays FALSE (never guessed true).
|
|
33
|
+
* - hasScaffold: the only source reference to the id sits beside a TODO/STUB/placeholder marker.
|
|
34
|
+
*/
|
|
35
|
+
export declare function deriveCodeEvidence(repoRoot: string, spec: SpecFile, row: ScorecardRow): CodeEvidenceResult;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.turbo']);
|
|
4
|
+
const SOURCE_FILE = /\.(ts|tsx|sql)$/;
|
|
5
|
+
const TEST_FILE = /\.(test|spec)\.tsx?$/;
|
|
6
|
+
/** Placeholder/stub markers — a file that only references the spec id near one of these is scaffold, not code. */
|
|
7
|
+
const SCAFFOLD_RE = /\b(TODO|FIXME|STUB|PLACEHOLDER|not[\s-]?implemented|coming soon)\b/i;
|
|
8
|
+
/**
|
|
9
|
+
* Extract candidate table/entity tokens (`<core>_<name>`) a spec declares. We read the Data Model
|
|
10
|
+
* `#### \`hr_xxx\`` headers and any `CREATE TABLE hr_xxx` / bare `hr_xxx` identifiers in the body.
|
|
11
|
+
* These tokens are the concrete strings we grep for in src + migrations to prove implementation.
|
|
12
|
+
* Conservative: when a spec declares no such token we return an empty set and the caller will NOT
|
|
13
|
+
* guess hasCode from the id alone unless the id itself is referenced in source.
|
|
14
|
+
*/
|
|
15
|
+
export function extractTableTokens(specText, core) {
|
|
16
|
+
const prefix = `${core.toLowerCase()}_`;
|
|
17
|
+
const tokens = new Set();
|
|
18
|
+
// Match identifiers that start with the core prefix and a letter, e.g. hr_employees, pm_encounters.
|
|
19
|
+
const re = new RegExp(`\\b${prefix}[a-z][a-z0-9_]*\\b`, 'g');
|
|
20
|
+
for (const m of specText.match(re) ?? []) {
|
|
21
|
+
// Drop trivially short / obviously non-table tokens (e.g. a bare prefix).
|
|
22
|
+
if (m.length > prefix.length + 1)
|
|
23
|
+
tokens.add(m);
|
|
24
|
+
}
|
|
25
|
+
return tokens;
|
|
26
|
+
}
|
|
27
|
+
/** Bounded recursive walk: invoke `onFile(text)` for every file under `dir` matching `fileFilter`.
|
|
28
|
+
* Stops early and returns true the first time `onFile` returns true. */
|
|
29
|
+
function walkSourceFiles(dir, fileFilter, onFile) {
|
|
30
|
+
if (!existsSync(dir))
|
|
31
|
+
return false;
|
|
32
|
+
const stack = [dir];
|
|
33
|
+
while (stack.length > 0) {
|
|
34
|
+
const cur = stack.pop();
|
|
35
|
+
if (cur === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
let entries;
|
|
38
|
+
try {
|
|
39
|
+
entries = readdirSync(cur, { withFileTypes: true });
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
for (const e of entries) {
|
|
45
|
+
if (e.isDirectory()) {
|
|
46
|
+
if (!SKIP_DIRS.has(e.name))
|
|
47
|
+
stack.push(join(cur, e.name));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (!fileFilter.test(e.name))
|
|
51
|
+
continue;
|
|
52
|
+
let text;
|
|
53
|
+
try {
|
|
54
|
+
text = readFileSync(join(cur, e.name), 'utf8');
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (onFile(text))
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/** Recursively test whether any source file under `dir` contains one of `needles`. */
|
|
66
|
+
function dirContainsAny(dir, needles, fileFilter) {
|
|
67
|
+
if (needles.length === 0)
|
|
68
|
+
return false;
|
|
69
|
+
return walkSourceFiles(dir, fileFilter, (text) => needles.some((n) => text.includes(n)));
|
|
70
|
+
}
|
|
71
|
+
/** Count files under `dir` (matching `fileFilter`) that mention `needle`. Informational only — used
|
|
72
|
+
* to surface an "id mentioned in N test files (unverified)" hint, never to decide a tier. */
|
|
73
|
+
function countFilesMentioning(dir, needle, fileFilter) {
|
|
74
|
+
let count = 0;
|
|
75
|
+
walkSourceFiles(dir, fileFilter, (text) => {
|
|
76
|
+
if (text.includes(needle))
|
|
77
|
+
count++;
|
|
78
|
+
return false; // never stop early — we want the full count
|
|
79
|
+
});
|
|
80
|
+
return count;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Classify how a `dir` references `needle`:
|
|
84
|
+
* - 'real' → at least one matching file references it WITHOUT being a scaffold/placeholder.
|
|
85
|
+
* - 'scaffold' → it is referenced, but every matching file is scaffold-only (TODO/STUB/placeholder).
|
|
86
|
+
* - 'none' → no matching file references it.
|
|
87
|
+
* Real wins over scaffold: a single non-stub reference is enough to call it real code.
|
|
88
|
+
*/
|
|
89
|
+
function classifyReference(dir, needle, fileFilter) {
|
|
90
|
+
let sawScaffold = false;
|
|
91
|
+
const foundReal = walkSourceFiles(dir, fileFilter, (text) => {
|
|
92
|
+
if (!text.includes(needle))
|
|
93
|
+
return false;
|
|
94
|
+
if (SCAFFOLD_RE.test(text)) {
|
|
95
|
+
sawScaffold = true;
|
|
96
|
+
return false; // keep scanning — a later file may be real code
|
|
97
|
+
}
|
|
98
|
+
return true; // non-scaffold reference → real
|
|
99
|
+
});
|
|
100
|
+
if (foundReal)
|
|
101
|
+
return 'real';
|
|
102
|
+
return sawScaffold ? 'scaffold' : 'none';
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Derive code/test evidence for one spec, CONSERVATIVELY (prefer surfacing uncertainty over an
|
|
106
|
+
* aggressive downgrade). Signals:
|
|
107
|
+
* - hasTestsPass: TRUE iff row.verifiedUndeclared > 0 — a genuine `@verifies` backlink exists for
|
|
108
|
+
* this spec but points at an undeclared/typo'd id (tests exist, linkage broken → T1/relink). This
|
|
109
|
+
* is the ONLY legitimate T1/relink case: when row.requirementsVerified > 0 classifyTier already
|
|
110
|
+
* returns T0, so a real declared backlink never reaches here. An incidental id-mention in a
|
|
111
|
+
* `tests/` file (a comment, fixture string, unrelated reference) is NOT proof of a passing test —
|
|
112
|
+
* it is surfaced as an informational hint in the reason only, and never sets hasTestsPass.
|
|
113
|
+
* - hasCode: a migration or `src/cores/<core>/` source file mentions one of the spec's declared
|
|
114
|
+
* table tokens, or references the spec id directly. When no reliable token can be extracted and
|
|
115
|
+
* the id is unreferenced, hasCode stays FALSE (never guessed true).
|
|
116
|
+
* - hasScaffold: the only source reference to the id sits beside a TODO/STUB/placeholder marker.
|
|
117
|
+
*/
|
|
118
|
+
export function deriveCodeEvidence(repoRoot, spec, row) {
|
|
119
|
+
const core = spec.core.toLowerCase();
|
|
120
|
+
let specText = '';
|
|
121
|
+
try {
|
|
122
|
+
specText = readFileSync(spec.path, 'utf8');
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// unreadable spec → no tokens; hasCode falls back to id-reference only
|
|
126
|
+
}
|
|
127
|
+
const tokens = extractTableTokens(specText, core);
|
|
128
|
+
const coreDir = join(repoRoot, 'src', 'cores', core);
|
|
129
|
+
const migDir = join(repoRoot, 'supabase', 'migrations');
|
|
130
|
+
// hasTestsPass — TRUE only for a genuine @verifies backlink that's mis-targeted (verifiedUndeclared>0).
|
|
131
|
+
// An incidental id-mention in a tests/ file is recorded as a HINT only; it never sets hasTestsPass.
|
|
132
|
+
const reasons = [];
|
|
133
|
+
let hasTestsPass = false;
|
|
134
|
+
if (row.verifiedUndeclared > 0) {
|
|
135
|
+
hasTestsPass = true;
|
|
136
|
+
reasons.push(`${row.verifiedUndeclared} @verifies backlink(s) to undeclared ids (tests exist, linkage broken)`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const mentions = countFilesMentioning(join(repoRoot, 'tests'), spec.id, TEST_FILE);
|
|
140
|
+
if (mentions > 0) {
|
|
141
|
+
reasons.push(`id mentioned in ${mentions} test file(s) (unverified — confirm in dogfood)`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// hasCode: declared table tokens in source/migrations, OR the spec id referenced (non-scaffold) in core source.
|
|
145
|
+
const needles = [...tokens];
|
|
146
|
+
let hasCode = false;
|
|
147
|
+
const idRef = classifyReference(coreDir, spec.id, SOURCE_FILE);
|
|
148
|
+
if (needles.length > 0 &&
|
|
149
|
+
(dirContainsAny(coreDir, needles, SOURCE_FILE) || dirContainsAny(migDir, needles, SOURCE_FILE))) {
|
|
150
|
+
hasCode = true;
|
|
151
|
+
reasons.push(`table token(s) [${needles.join(', ')}] found in src/cores/${core} or migrations`);
|
|
152
|
+
}
|
|
153
|
+
else if (idRef === 'real') {
|
|
154
|
+
hasCode = true;
|
|
155
|
+
reasons.push(`spec id ${spec.id} referenced in src/cores/${core}`);
|
|
156
|
+
}
|
|
157
|
+
else if (needles.length === 0) {
|
|
158
|
+
reasons.push('no reliable table token extracted and id unreferenced in core source → hasCode=false (conservative)');
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
reasons.push(`no match for table token(s) [${needles.join(', ')}] or id in core source/migrations`);
|
|
162
|
+
}
|
|
163
|
+
// hasScaffold: only meaningful when we did NOT find real code. A TODO-adjacent id reference.
|
|
164
|
+
let hasScaffold = false;
|
|
165
|
+
if (!hasCode && idRef === 'scaffold') {
|
|
166
|
+
hasScaffold = true;
|
|
167
|
+
reasons.push(`scaffold/placeholder reference to ${spec.id} in src/cores/${core}`);
|
|
168
|
+
}
|
|
169
|
+
return { hasTestsPass, hasCode, hasScaffold, reason: reasons.join('; ') };
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=code-evidence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-evidence.js","sourceRoot":"","sources":["../../src/lib/code-evidence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type CoherenceWarning = {
|
|
2
|
+
type: 'missing-table-migration' | 'missing-task-path' | 'missing-test-pattern';
|
|
3
|
+
message: string;
|
|
4
|
+
};
|
|
5
|
+
export type CoherenceReport = {
|
|
6
|
+
warnings: CoherenceWarning[];
|
|
7
|
+
};
|
|
8
|
+
/** A spec-referenced table that already exists in a committed migration. */
|
|
9
|
+
export type ExistingTableRef = {
|
|
10
|
+
table: string;
|
|
11
|
+
migration: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function findExistingTableReferences(repoRoot: string, specId: string, core: string): ExistingTableRef[];
|
|
14
|
+
export declare function evaluateCoherence(repoRoot: string, specId: string, core: string): CoherenceReport;
|