@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,40 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { coresList, specsRoot } from './repo.js';
|
|
4
|
+
import { extractSpecId, isDerivativeArtifact } from './specs.js';
|
|
5
|
+
export function findMisplacedSpecs(repoRoot) {
|
|
6
|
+
const out = [];
|
|
7
|
+
for (const core of coresList(repoRoot)) {
|
|
8
|
+
const coreDir = join(specsRoot(repoRoot), core);
|
|
9
|
+
if (!existsSync(coreDir))
|
|
10
|
+
continue;
|
|
11
|
+
const stack = [coreDir];
|
|
12
|
+
while (stack.length > 0) {
|
|
13
|
+
const dir = stack.pop();
|
|
14
|
+
const dirName = basename(dir);
|
|
15
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
16
|
+
const full = join(dir, entry.name);
|
|
17
|
+
if (entry.isSymbolicLink())
|
|
18
|
+
continue; // skip symlinks to avoid loops
|
|
19
|
+
if (entry.isDirectory()) {
|
|
20
|
+
stack.push(full);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (!entry.name.endsWith('.md'))
|
|
24
|
+
continue;
|
|
25
|
+
if (isDerivativeArtifact(dirName, entry.name))
|
|
26
|
+
continue;
|
|
27
|
+
const id = extractSpecId(entry.name, core);
|
|
28
|
+
if (!id)
|
|
29
|
+
continue;
|
|
30
|
+
const rel = full.slice(coreDir.length + 1);
|
|
31
|
+
const inCanonical = /^(?:specs|ux|archive)[\\/]/.test(rel);
|
|
32
|
+
if (!inCanonical) {
|
|
33
|
+
out.push({ id, path: full, expectedDir: join(coreDir, 'specs') });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=placement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placement.js","sourceRoot":"","sources":["../../src/lib/placement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAIjE,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,SAAS;QACnC,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC;QACxB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;YACzB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC9B,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBACnC,IAAI,KAAK,CAAC,cAAc,EAAE;oBAAE,SAAS,CAAC,+BAA+B;gBACrE,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;oBACxB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACjB,SAAS;gBACX,CAAC;gBACD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;oBAAE,SAAS;gBAC1C,IAAI,oBAAoB,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC;oBAAE,SAAS;gBACxD,MAAM,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC3C,IAAI,CAAC,EAAE;oBAAE,SAAS;gBAClB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC3C,MAAM,WAAW,GAAG,4BAA4B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC3D,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;gBACpE,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** One ranked hit from the lexical precedent search over specs/spec-index.json. */
|
|
2
|
+
export interface PrecedentHit {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
file: string;
|
|
6
|
+
core: string;
|
|
7
|
+
status: string;
|
|
8
|
+
score: number;
|
|
9
|
+
matchedFields: string[];
|
|
10
|
+
}
|
|
11
|
+
/** Ranked lexical search over specs/spec-index.json (no markdown re-parse).
|
|
12
|
+
* Throws when the index is missing — callers that must fail open catch. */
|
|
13
|
+
export declare function searchSpecs(topic: string, opts: {
|
|
14
|
+
core?: string;
|
|
15
|
+
repoRoot: string;
|
|
16
|
+
includeArchived?: boolean;
|
|
17
|
+
limit?: number;
|
|
18
|
+
}): PrecedentHit[];
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const DEFAULT_LIMIT = 15;
|
|
4
|
+
// Score tiers, strongest first: word-boundary phrase > all-tokens > partial
|
|
5
|
+
// (per matched token, title x2). The body's all-tokens tier is cross-field-UNION
|
|
6
|
+
// based: tokens matched anywhere across the body fields are unioned, and the
|
|
7
|
+
// tier fires when that union covers every query token — no single field has to
|
|
8
|
+
// contain them all. Title and body are scored independently and summed, with
|
|
9
|
+
// one cross-field rule: when the TITLE's winning tier is the partial tier, the
|
|
10
|
+
// tokens it matched are "claimed" and earn no second partial credit in the body
|
|
11
|
+
// — otherwise a single token scattered across title + body (2 + 1) would tie or
|
|
12
|
+
// beat a genuine all-tokens body match (3). When the title wins via
|
|
13
|
+
// phrase/all-tokens, body partial credit is unrestricted.
|
|
14
|
+
const TITLE_WEIGHTS = { phrase: 10, all: 6, perToken: 2 };
|
|
15
|
+
const BODY_WEIGHTS = { phrase: 5, all: 3, perToken: 1 };
|
|
16
|
+
// Index body fields scored, in matchedFields reporting order (after 'title').
|
|
17
|
+
// `summary` is bounded Summary/Overview prose: the structured arrays index only a
|
|
18
|
+
// thin slice, so the domain vocabulary paraphrase queries hit lives here. Partial
|
|
19
|
+
// credit is per-distinct-query-token (capped at the token count), so a longer
|
|
20
|
+
// summary lifts recall without inflating any single spec's score past coverage.
|
|
21
|
+
const BODY_FIELDS = [
|
|
22
|
+
'summary',
|
|
23
|
+
'user_stories',
|
|
24
|
+
'acceptance_criteria',
|
|
25
|
+
'functional_requirements',
|
|
26
|
+
'non_functional_requirements',
|
|
27
|
+
'success_metrics',
|
|
28
|
+
];
|
|
29
|
+
function escapeRegExp(s) {
|
|
30
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
31
|
+
}
|
|
32
|
+
/** Lowercased tokens; length >= 2 so acronyms (AI, EDI, MAT, UDS) survive. */
|
|
33
|
+
function tokenize(topic) {
|
|
34
|
+
return topic
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.split(/[^a-z0-9]+/)
|
|
37
|
+
.filter((t) => t.length >= 2);
|
|
38
|
+
}
|
|
39
|
+
/** Whole-topic phrase as a word-boundary regex; punctuation between words is
|
|
40
|
+
* flexible, so "credentialing compliance" matches "Credentialing & Compliance". */
|
|
41
|
+
function phraseRegex(tokens) {
|
|
42
|
+
if (tokens.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
return new RegExp(`(?:^|[^a-z0-9])${tokens.map(escapeRegExp).join('[^a-z0-9]+')}(?:$|[^a-z0-9])`);
|
|
45
|
+
}
|
|
46
|
+
function tokenRegexes(tokens) {
|
|
47
|
+
return tokens.map((t) => new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(t)}(?:$|[^a-z0-9])`));
|
|
48
|
+
}
|
|
49
|
+
/** Runtime guard for untrusted index fields: anything non-array becomes []. */
|
|
50
|
+
function asArray(value) {
|
|
51
|
+
return Array.isArray(value) ? value : [];
|
|
52
|
+
}
|
|
53
|
+
/** ACs carry given/when/then arrays instead of a .text field — flatten them. */
|
|
54
|
+
function acText(ac) {
|
|
55
|
+
if (!ac)
|
|
56
|
+
return '';
|
|
57
|
+
return [ac.title ?? '', ...asArray(ac.given), ...asArray(ac.when), ...asArray(ac.then)].join(' ');
|
|
58
|
+
}
|
|
59
|
+
function fieldText(spec, field) {
|
|
60
|
+
if (field === 'summary')
|
|
61
|
+
return spec.summary ?? '';
|
|
62
|
+
if (field === 'acceptance_criteria')
|
|
63
|
+
return asArray(spec.acceptance_criteria).map(acText).join('\n');
|
|
64
|
+
return asArray(spec[field])
|
|
65
|
+
.map((i) => i?.text ?? '')
|
|
66
|
+
.join('\n');
|
|
67
|
+
}
|
|
68
|
+
function isArchived(spec) {
|
|
69
|
+
return spec.status === 'archived' || (spec.file ?? '').includes('/archive/');
|
|
70
|
+
}
|
|
71
|
+
/** Token indexes (into the query token list) that word-boundary match the haystack. */
|
|
72
|
+
function matchedTokenIdxs(haystack, tokenRes) {
|
|
73
|
+
const matched = new Set();
|
|
74
|
+
if (!haystack)
|
|
75
|
+
return matched;
|
|
76
|
+
tokenRes.forEach((re, i) => {
|
|
77
|
+
if (re.test(haystack))
|
|
78
|
+
matched.add(i);
|
|
79
|
+
});
|
|
80
|
+
return matched;
|
|
81
|
+
}
|
|
82
|
+
/** Ranked lexical search over specs/spec-index.json (no markdown re-parse).
|
|
83
|
+
* Throws when the index is missing — callers that must fail open catch. */
|
|
84
|
+
export function searchSpecs(topic, opts) {
|
|
85
|
+
const indexPath = join(opts.repoRoot, 'specs', 'spec-index.json');
|
|
86
|
+
if (!existsSync(indexPath)) {
|
|
87
|
+
throw new Error(`spec index not found at ${indexPath} — run \`npm run spec:index\` first`);
|
|
88
|
+
}
|
|
89
|
+
const tokens = tokenize(topic);
|
|
90
|
+
if (tokens.length === 0)
|
|
91
|
+
return [];
|
|
92
|
+
const phrase = phraseRegex(tokens);
|
|
93
|
+
const tokenRes = tokenRegexes(tokens);
|
|
94
|
+
const core = opts.core?.toLowerCase();
|
|
95
|
+
const limit = opts.limit ?? DEFAULT_LIMIT;
|
|
96
|
+
const index = JSON.parse(readFileSync(indexPath, 'utf8'));
|
|
97
|
+
const hits = [];
|
|
98
|
+
for (const spec of asArray(index.specs)) {
|
|
99
|
+
if (!spec)
|
|
100
|
+
continue;
|
|
101
|
+
if (core && spec.core?.toLowerCase() !== core)
|
|
102
|
+
continue;
|
|
103
|
+
if (!opts.includeArchived && isArchived(spec))
|
|
104
|
+
continue;
|
|
105
|
+
// --- Title ---
|
|
106
|
+
const title = (spec.title ?? '').toLowerCase();
|
|
107
|
+
const titleTokens = matchedTokenIdxs(title, tokenRes);
|
|
108
|
+
const titleHolistic = Math.max(phrase?.test(title) ? TITLE_WEIGHTS.phrase : 0, titleTokens.size === tokens.length && tokens.length > 0 ? TITLE_WEIGHTS.all : 0);
|
|
109
|
+
const titlePartial = titleTokens.size * TITLE_WEIGHTS.perToken;
|
|
110
|
+
const titleScore = Math.max(titleHolistic, titlePartial);
|
|
111
|
+
// Partial tier won the title → those tokens are claimed (see weights note).
|
|
112
|
+
const titleClaimsTokens = titlePartial > titleHolistic;
|
|
113
|
+
// --- Body (per-field, then unioned) ---
|
|
114
|
+
const fieldTexts = BODY_FIELDS.map((f) => fieldText(spec, f).toLowerCase());
|
|
115
|
+
const fieldPhrase = fieldTexts.map((t) => (t !== '' && phrase ? phrase.test(t) : false));
|
|
116
|
+
const fieldTokens = fieldTexts.map((t) => matchedTokenIdxs(t, tokenRes));
|
|
117
|
+
const bodyTokens = new Set(fieldTokens.flatMap((s) => [...s]));
|
|
118
|
+
const countedTokens = new Set([...bodyTokens].filter((i) => !(titleClaimsTokens && titleTokens.has(i))));
|
|
119
|
+
const bodyPhrase = fieldPhrase.some(Boolean);
|
|
120
|
+
const bodyAll = bodyTokens.size === tokens.length && tokens.length > 0;
|
|
121
|
+
const bodyPartial = countedTokens.size * BODY_WEIGHTS.perToken;
|
|
122
|
+
const bodyScore = Math.max(bodyPhrase ? BODY_WEIGHTS.phrase : 0, bodyAll ? BODY_WEIGHTS.all : 0, bodyPartial);
|
|
123
|
+
const score = titleScore + bodyScore;
|
|
124
|
+
if (score <= 0)
|
|
125
|
+
continue;
|
|
126
|
+
// matchedFields = title (if it scored) + body fields contributing to the
|
|
127
|
+
// WINNING body tier — a stray single token in another field is not a match.
|
|
128
|
+
const matchedFields = [];
|
|
129
|
+
if (titleScore > 0)
|
|
130
|
+
matchedFields.push('title');
|
|
131
|
+
if (bodyScore > 0) {
|
|
132
|
+
const tier = bodyPhrase && bodyScore === BODY_WEIGHTS.phrase
|
|
133
|
+
? 'phrase'
|
|
134
|
+
: bodyAll && bodyScore === BODY_WEIGHTS.all
|
|
135
|
+
? 'all'
|
|
136
|
+
: 'partial';
|
|
137
|
+
BODY_FIELDS.forEach((field, i) => {
|
|
138
|
+
const contributes = tier === 'phrase'
|
|
139
|
+
? fieldPhrase[i]
|
|
140
|
+
: tier === 'all'
|
|
141
|
+
? fieldTokens[i].size > 0
|
|
142
|
+
: [...fieldTokens[i]].some((t) => countedTokens.has(t));
|
|
143
|
+
if (contributes)
|
|
144
|
+
matchedFields.push(field);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
hits.push({
|
|
148
|
+
id: spec.id,
|
|
149
|
+
title: spec.title ?? '',
|
|
150
|
+
file: spec.file ?? '',
|
|
151
|
+
core: spec.core,
|
|
152
|
+
status: spec.status ?? 'unknown',
|
|
153
|
+
score,
|
|
154
|
+
matchedFields,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
hits.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
158
|
+
return hits.slice(0, limit);
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=precedent-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"precedent-search.js","sourceRoot":"","sources":["../../src/lib/precedent-search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PredicateRegistry } from './pipeline-schema.js';
|
|
2
|
+
/** The default named-predicate registry for the encoreos-1 pipeline. Each body is
|
|
3
|
+
* the EXACT `isDone` closure lifted from the current `PIPELINE` in pipeline-map.ts,
|
|
4
|
+
* plus the two `computeStage` cap predicates (`hasResearch`/`hasAcceptanceCriteria`,
|
|
5
|
+
* reused as caps). Field names verified against SpecFile/SpecSignals in specs.ts. */
|
|
6
|
+
export declare const DEFAULT_PREDICATES: PredicateRegistry;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** The default named-predicate registry for the encoreos-1 pipeline. Each body is
|
|
2
|
+
* the EXACT `isDone` closure lifted from the current `PIPELINE` in pipeline-map.ts,
|
|
3
|
+
* plus the two `computeStage` cap predicates (`hasResearch`/`hasAcceptanceCriteria`,
|
|
4
|
+
* reused as caps). Field names verified against SpecFile/SpecSignals in specs.ts. */
|
|
5
|
+
export const DEFAULT_PREDICATES = {
|
|
6
|
+
// clarify-spec: CONTEXT.md is the clarify artifact when present (top-level flag).
|
|
7
|
+
hasContext: (s) => s.hasContext,
|
|
8
|
+
// validate-spec: <!-- validated: --> marker.
|
|
9
|
+
hasValidationSignoff: (s) => s.signals.hasValidationSignoff,
|
|
10
|
+
// ground-research (regulatedOnly) — also reused as the research cap predicate.
|
|
11
|
+
hasResearch: (s) => s.signals.hasResearch,
|
|
12
|
+
// spec-review.
|
|
13
|
+
hasSpecReview: (s) => s.signals.hasSpecReview,
|
|
14
|
+
// compliance-review (regulatedOnly).
|
|
15
|
+
hasComplianceSignoff: (s) => s.signals.hasComplianceSignoff,
|
|
16
|
+
// draft-acs: done when there are no user stories, or ACs are drafted.
|
|
17
|
+
acsDraftedOrNoStories: (s) => !s.signals.hasUserStories || s.signals.hasAcceptanceCriteria,
|
|
18
|
+
// generate-tests: ACs absent, OR an active generated suite / verified ACs / linked local test exists (B5 + C1).
|
|
19
|
+
testsGeneratedOrProven: (s) => !s.signals.hasAcceptanceCriteria ||
|
|
20
|
+
s.signals.hasActiveGeneratedTests ||
|
|
21
|
+
s.signals.hasVerifiedAcceptanceCriteria ||
|
|
22
|
+
s.signals.hasLinkedLocalTest,
|
|
23
|
+
// generate-tests (BUILDER layout): keys on hasGeneratedTests (e2e_tests LISTED)
|
|
24
|
+
// rather than the fs-verified hasActiveGeneratedTests — matching the retired
|
|
25
|
+
// builder fork's gate, and computable by a read-economy consumer (Spec Studio)
|
|
26
|
+
// that can't read the test files to confirm an active suite.
|
|
27
|
+
testsListedOrProven: (s) => !s.signals.hasAcceptanceCriteria ||
|
|
28
|
+
s.signals.hasGeneratedTests ||
|
|
29
|
+
s.signals.hasVerifiedAcceptanceCriteria ||
|
|
30
|
+
s.signals.hasLinkedLocalTest,
|
|
31
|
+
// a11y-audit (optional, completes ux_reviewed).
|
|
32
|
+
hasUxSignoff: (s) => s.signals.hasUxSignoff,
|
|
33
|
+
// create-plan (top-level flag).
|
|
34
|
+
hasPlan: (s) => s.hasPlan,
|
|
35
|
+
// generate-tasks (top-level flag).
|
|
36
|
+
hasTasks: (s) => s.hasTasks,
|
|
37
|
+
// AC cap predicate (computeStage): caps below tasks_generated when ACs are absent.
|
|
38
|
+
hasAcceptanceCriteria: (s) => s.signals.hasAcceptanceCriteria,
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=predicate-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"predicate-registry.js","sourceRoot":"","sources":["../../src/lib/predicate-registry.ts"],"names":[],"mappings":"AAEA;;;sFAGsF;AACtF,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,kFAAkF;IAClF,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU;IAC/B,6CAA6C;IAC7C,oBAAoB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB;IAC3D,+EAA+E;IAC/E,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW;IACzC,eAAe;IACf,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa;IAC7C,qCAAqC;IACrC,oBAAoB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB;IAC3D,sEAAsE;IACtE,qBAAqB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC,OAAO,CAAC,qBAAqB;IAC1F,gHAAgH;IAChH,sBAAsB,EAAE,CAAC,CAAC,EAAE,EAAE,CAC5B,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB;QAChC,CAAC,CAAC,OAAO,CAAC,uBAAuB;QACjC,CAAC,CAAC,OAAO,CAAC,6BAA6B;QACvC,CAAC,CAAC,OAAO,CAAC,kBAAkB;IAC9B,gFAAgF;IAChF,6EAA6E;IAC7E,+EAA+E;IAC/E,6DAA6D;IAC7D,mBAAmB,EAAE,CAAC,CAAC,EAAE,EAAE,CACzB,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB;QAChC,CAAC,CAAC,OAAO,CAAC,iBAAiB;QAC3B,CAAC,CAAC,OAAO,CAAC,6BAA6B;QACvC,CAAC,CAAC,OAAO,CAAC,kBAAkB;IAC9B,gDAAgD;IAChD,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY;IAC3C,gCAAgC;IAChC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO;IACzB,mCAAmC;IACnC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ;IAC3B,mFAAmF;IACnF,qBAAqB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB;CAC9D,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 8 precedent-recall benchmark cases (8 exact-wording + 8 paraphrase queries),
|
|
3
|
+
* from the 2026-06-09 dogfood. Shared by the lexical recall test, the semantic
|
|
4
|
+
* recall test, and the embedding build script (which embeds these queries into the
|
|
5
|
+
* committed `recall-query-vectors.json` fixture) so all three stay in lockstep.
|
|
6
|
+
*
|
|
7
|
+
* Targets are matched by FILE, not id (RH-06-EN-3 carries the parent id RH-06).
|
|
8
|
+
* If a target spec is renamed, update its `targetFile`; never delete a case.
|
|
9
|
+
*/
|
|
10
|
+
export interface RecallCase {
|
|
11
|
+
exact: string;
|
|
12
|
+
paraphrase: string;
|
|
13
|
+
targetFile: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const RECALL_CASES: RecallCase[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const RECALL_CASES = [
|
|
2
|
+
{
|
|
3
|
+
exact: 'exit interview',
|
|
4
|
+
paraphrase: 'staff offboarding termination',
|
|
5
|
+
targetFile: 'HR-41-employee-separation-exit-interview-management.md',
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
exact: 'fixed assets depreciation',
|
|
9
|
+
paraphrase: 'capital equipment amortization',
|
|
10
|
+
targetFile: 'FA-11-fixed-assets-depreciation.md',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
exact: 'fire inspection',
|
|
14
|
+
paraphrase: 'building safety walkthrough',
|
|
15
|
+
targetFile: 'RH-06-EN-3-fire-inspection-compliance.md',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
exact: 'denial prediction',
|
|
19
|
+
paraphrase: 'claim rejection forecasting',
|
|
20
|
+
targetFile: 'PM-50-ai-denial-prediction-prevention.md',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
exact: 'consent management',
|
|
24
|
+
paraphrase: 'patient authorization privacy releases',
|
|
25
|
+
targetFile: 'CL-11-consent-management-42cfr-part2.md',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
exact: 'predictive maintenance',
|
|
29
|
+
paraphrase: 'equipment failure forecasting',
|
|
30
|
+
targetFile: 'FM-28-predictive-maintenance-ai.md',
|
|
31
|
+
},
|
|
32
|
+
{ exact: 'incident reporting', paraphrase: 'adverse event tracking', targetFile: 'GR-09-incident-reporting.md' },
|
|
33
|
+
{
|
|
34
|
+
exact: 'tamper-evident hash chain',
|
|
35
|
+
paraphrase: 'immutable audit log integrity',
|
|
36
|
+
targetFile: 'FW-62-audit-trail-tamper-evident-hash-chain.md',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
//# sourceMappingURL=recall-cases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-cases.js","sourceRoot":"","sources":["../../src/lib/recall-cases.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC;QACE,KAAK,EAAE,gBAAgB;QACvB,UAAU,EAAE,+BAA+B;QAC3C,UAAU,EAAE,wDAAwD;KACrE;IACD;QACE,KAAK,EAAE,2BAA2B;QAClC,UAAU,EAAE,gCAAgC;QAC5C,UAAU,EAAE,oCAAoC;KACjD;IACD;QACE,KAAK,EAAE,iBAAiB;QACxB,UAAU,EAAE,6BAA6B;QACzC,UAAU,EAAE,0CAA0C;KACvD;IACD;QACE,KAAK,EAAE,mBAAmB;QAC1B,UAAU,EAAE,6BAA6B;QACzC,UAAU,EAAE,0CAA0C;KACvD;IACD;QACE,KAAK,EAAE,oBAAoB;QAC3B,UAAU,EAAE,wCAAwC;QACpD,UAAU,EAAE,yCAAyC;KACtD;IACD;QACE,KAAK,EAAE,wBAAwB;QAC/B,UAAU,EAAE,+BAA+B;QAC3C,UAAU,EAAE,oCAAoC;KACjD;IACD,EAAE,KAAK,EAAE,oBAAoB,EAAE,UAAU,EAAE,wBAAwB,EAAE,UAAU,EAAE,6BAA6B,EAAE;IAChH;QACE,KAAK,EAAE,2BAA2B;QAClC,UAAU,EAAE,+BAA+B;QAC3C,UAAU,EAAE,gDAAgD;KAC7D;CACF,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare function getRepoRoot(override?: string): string;
|
|
2
|
+
export declare function specsRoot(repoRoot: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Canonical fallback core ids. Mirrors `scripts/database/core-prefixes.ts` (CORE_PREFIXES).
|
|
5
|
+
* A direct cross-workspace import is not possible: this package compiles with
|
|
6
|
+
* `rootDir: ./src` (TS6059 on any import outside src/), and `bin/eos-spec.js` runs compiled
|
|
7
|
+
* `dist/` under plain node, which cannot load a `.ts` file from `scripts/` at runtime.
|
|
8
|
+
* Drift protection: repo.test.ts parses core-prefixes.ts at test time and asserts equality.
|
|
9
|
+
*/
|
|
10
|
+
export declare const FALLBACK_CORES: string[];
|
|
11
|
+
export declare function coresList(repoRoot?: string): string[];
|
|
12
|
+
/**
|
|
13
|
+
* Membership check against the configured core list — intentionally NOT dir-filtered:
|
|
14
|
+
* `create` must accept the first spec of a new core before its specs/{core}/ dir exists.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isKnownCore(core: string, repoRoot?: string): boolean;
|
|
17
|
+
/** Current git branch for repoRoot, or '' if undeterminable. */
|
|
18
|
+
export declare function currentBranch(repoRoot: string): string;
|
|
19
|
+
/** True on main/master — commands that mutate tracked files must refuse to --write here. */
|
|
20
|
+
export declare function isProtectedBranch(repoRoot: string): boolean;
|
package/dist/lib/repo.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { coresFromConfig, isKnownCoreFromConfig, loadConfig } from './config.js';
|
|
5
|
+
let cachedRepoRoot = null;
|
|
6
|
+
export function getRepoRoot(override) {
|
|
7
|
+
if (override) {
|
|
8
|
+
if (!existsSync(override)) {
|
|
9
|
+
throw new Error(`Repo root override does not exist: ${override}`);
|
|
10
|
+
}
|
|
11
|
+
return override;
|
|
12
|
+
}
|
|
13
|
+
if (cachedRepoRoot)
|
|
14
|
+
return cachedRepoRoot;
|
|
15
|
+
try {
|
|
16
|
+
cachedRepoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
|
|
17
|
+
return cachedRepoRoot;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new Error('Could not determine repo root. Run from inside the encoreos repo or pass --repo-root.');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function specsRoot(repoRoot) {
|
|
24
|
+
return join(repoRoot, 'specs');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Canonical fallback core ids. Mirrors `scripts/database/core-prefixes.ts` (CORE_PREFIXES).
|
|
28
|
+
* A direct cross-workspace import is not possible: this package compiles with
|
|
29
|
+
* `rootDir: ./src` (TS6059 on any import outside src/), and `bin/eos-spec.js` runs compiled
|
|
30
|
+
* `dist/` under plain node, which cannot load a `.ts` file from `scripts/` at runtime.
|
|
31
|
+
* Drift protection: repo.test.ts parses core-prefixes.ts at test time and asserts equality.
|
|
32
|
+
*/
|
|
33
|
+
export const FALLBACK_CORES = ['pf', 'ce', 'rh', 'fa', 'hr', 'gr', 'fw', 'fm', 'lo', 'it', 'cl', 'pm'];
|
|
34
|
+
/** Keep only cores that actually have a `specs/{core}/` directory. specs/config.yaml lists
|
|
35
|
+
* `tr` (transcription module), which has no specs dir — scanning it yields phantom empty cores. */
|
|
36
|
+
function withSpecsDir(cores, repoRoot) {
|
|
37
|
+
return cores.filter((core) => existsSync(join(repoRoot, 'specs', core)));
|
|
38
|
+
}
|
|
39
|
+
export function coresList(repoRoot) {
|
|
40
|
+
if (repoRoot) {
|
|
41
|
+
try {
|
|
42
|
+
const config = loadConfig(repoRoot);
|
|
43
|
+
return withSpecsDir(coresFromConfig(config), repoRoot);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// config unreadable — fall back to the canonical list, still dir-filtered
|
|
47
|
+
}
|
|
48
|
+
return withSpecsDir(FALLBACK_CORES, repoRoot);
|
|
49
|
+
}
|
|
50
|
+
return FALLBACK_CORES;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Membership check against the configured core list — intentionally NOT dir-filtered:
|
|
54
|
+
* `create` must accept the first spec of a new core before its specs/{core}/ dir exists.
|
|
55
|
+
*/
|
|
56
|
+
export function isKnownCore(core, repoRoot) {
|
|
57
|
+
if (repoRoot) {
|
|
58
|
+
try {
|
|
59
|
+
const config = loadConfig(repoRoot);
|
|
60
|
+
return isKnownCoreFromConfig(config, core);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// fall through
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return FALLBACK_CORES.includes(core.toLowerCase());
|
|
67
|
+
}
|
|
68
|
+
/** Current git branch for repoRoot, or '' if undeterminable. */
|
|
69
|
+
export function currentBranch(repoRoot) {
|
|
70
|
+
try {
|
|
71
|
+
return execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf8' }).trim();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** True on main/master — commands that mutate tracked files must refuse to --write here. */
|
|
78
|
+
export function isProtectedBranch(repoRoot) {
|
|
79
|
+
const b = currentBranch(repoRoot);
|
|
80
|
+
return b === 'main' || b === 'master';
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=repo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/lib/repo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed acceptance-criteria / user-story / functional-requirement schema for spec
|
|
3
|
+
* frontmatter, plus a lenient normalizer. Adapted from encoreos's
|
|
4
|
+
* `ac-response-parser.ts`, but reads already-parsed YAML frontmatter values
|
|
5
|
+
* (objects) rather than an LLM JSON response, and coerces scalar Given/When/Then
|
|
6
|
+
* strings into single-element arrays so scalar-shaped legacy specs parse unchanged.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* AC/US/FR status lifecycle. Two of these are terminal-but-not-auto-proven and
|
|
10
|
+
* the audit's "AC Validated" gate treats them as closed (TOOL — issue #174):
|
|
11
|
+
* - `hand_closed`: proven by a CI suite the src-lane `verify-acs` prover can't
|
|
12
|
+
* reach (runner/tool/e2e). REQUIRES a `## Verification` backlink in the spec
|
|
13
|
+
* (the audit flags a hand_closed AC with no Verification section as a violation).
|
|
14
|
+
* - `deferred`: an explicit planned-by-design non-goal.
|
|
15
|
+
* A genuinely un-proven AC still reads `planned` and still fails the gate.
|
|
16
|
+
*/
|
|
17
|
+
export type RequirementStatus = 'planned' | 'verified' | 'hand_closed' | 'deferred';
|
|
18
|
+
/** The status values the audit's "AC Validated" gate accepts as closed
|
|
19
|
+
* (everything except `planned`). */
|
|
20
|
+
export declare const TERMINAL_AC_STATUSES: readonly RequirementStatus[];
|
|
21
|
+
export type AcceptanceCriterion = {
|
|
22
|
+
id: string;
|
|
23
|
+
userStory?: string;
|
|
24
|
+
status: RequirementStatus;
|
|
25
|
+
given: string[];
|
|
26
|
+
when: string[];
|
|
27
|
+
then: string[];
|
|
28
|
+
statementOnly: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type UserStory = {
|
|
31
|
+
id: string;
|
|
32
|
+
text: string;
|
|
33
|
+
};
|
|
34
|
+
export type Requirement = {
|
|
35
|
+
id: string;
|
|
36
|
+
text: string;
|
|
37
|
+
status: RequirementStatus;
|
|
38
|
+
};
|
|
39
|
+
/** A structural problem found while normalizing — mapped to a ValidateIssue by the caller. */
|
|
40
|
+
export type ShapeIssue = {
|
|
41
|
+
id?: string;
|
|
42
|
+
field: string;
|
|
43
|
+
message: string;
|
|
44
|
+
};
|
|
45
|
+
export declare const AC_ID_RE: RegExp;
|
|
46
|
+
export declare const US_ID_RE: RegExp;
|
|
47
|
+
export declare const FR_ID_RE: RegExp;
|
|
48
|
+
/** Scalar string -> `[string]`; array -> trimmed non-empty strings; anything else -> `[]`. */
|
|
49
|
+
export declare function coerceStringArray(v: unknown): string[];
|
|
50
|
+
/**
|
|
51
|
+
* Normalizes a raw YAML frontmatter `acceptance_criteria` value into typed
|
|
52
|
+
* `AcceptanceCriterion` objects, collecting `ShapeIssue`s for any structural
|
|
53
|
+
* problems found. An entry with an unrecognized status is still included with
|
|
54
|
+
* `status` defaulted to `'planned'` plus a ShapeIssue — callers must check
|
|
55
|
+
* `issues` before trusting `status`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function normalizeAcceptanceCriteria(raw: unknown): {
|
|
58
|
+
acs: AcceptanceCriterion[];
|
|
59
|
+
issues: ShapeIssue[];
|
|
60
|
+
};
|
|
61
|
+
export declare function normalizeUserStories(raw: unknown): {
|
|
62
|
+
uss: UserStory[];
|
|
63
|
+
issues: ShapeIssue[];
|
|
64
|
+
};
|
|
65
|
+
export declare function normalizeRequirements(raw: unknown): {
|
|
66
|
+
frs: Requirement[];
|
|
67
|
+
issues: ShapeIssue[];
|
|
68
|
+
};
|
|
69
|
+
/** Per-AC status tally for the pipeline status reader (consumed by the P3 audit).
|
|
70
|
+
* `hand_closed` and `deferred` are terminal-but-not-`planned` statuses the audit
|
|
71
|
+
* treats as closed (issue #174); they are counted separately so the audit can
|
|
72
|
+
* still apply the Verification-backlink guard to `hand_closed`. */
|
|
73
|
+
export declare function acStatusCounts(raw: unknown): AcStatusCounts;
|
|
74
|
+
export type AcStatusCounts = {
|
|
75
|
+
planned: number;
|
|
76
|
+
verified: number;
|
|
77
|
+
handClosed: number;
|
|
78
|
+
deferred: number;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* True when a spec has ≥1 acceptance criterion and EVERY (well-formed) AC is in a
|
|
82
|
+
* terminal status (`verified` | `hand_closed` | `deferred`) — i.e. none remain
|
|
83
|
+
* `planned`. This is the typed, ADR-0015-aware replacement for the legacy
|
|
84
|
+
* `status === 'verified'` check `specs.ts` used to hand-roll: a `hand_closed`
|
|
85
|
+
* (proven by a CI suite the src-lane prover can't reach) or `deferred` AC
|
|
86
|
+
* terminally resolves the generate-tests gate exactly as a `verified` one does
|
|
87
|
+
* (issue #174). Malformed (id-less) AC entries are dropped by
|
|
88
|
+
* `normalizeAcceptanceCriteria` — and `validate` flags them — so they are not
|
|
89
|
+
* counted here; an empty / non-array value is `false`.
|
|
90
|
+
*/
|
|
91
|
+
export declare function allAcceptanceCriteriaTerminal(raw: unknown): boolean;
|