@ai-sdlc/orchestrator 0.10.0 → 0.13.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/dist/adapters.d.ts +42 -3
- package/dist/adapters.js +133 -3
- package/dist/admission-composite.d.ts +112 -1
- package/dist/admission-composite.js +85 -4
- package/dist/admission-enrichment.d.ts +12 -3
- package/dist/admission-enrichment.js +64 -13
- package/dist/admission-score.d.ts +30 -0
- package/dist/admission-score.js +4 -1
- package/dist/backlog-adapter.d.ts +45 -0
- package/dist/backlog-adapter.js +145 -1
- package/dist/calibration.d.ts +81 -0
- package/dist/calibration.js +76 -0
- package/dist/cli/commands/init-features.d.ts +301 -2
- package/dist/cli/commands/init-features.js +634 -7
- package/dist/cli/commands/init-templates.d.ts +198 -1
- package/dist/cli/commands/init-templates.js +943 -1
- package/dist/cli/commands/init.d.ts +45 -0
- package/dist/cli/commands/init.js +147 -5
- package/dist/cli/commands/run.js +9 -1
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.js +42 -8
- package/dist/compliance/composer.d.ts +79 -0
- package/dist/compliance/composer.js +258 -0
- package/dist/compliance/errors.d.ts +64 -0
- package/dist/compliance/errors.js +85 -0
- package/dist/compliance/loader.d.ts +52 -0
- package/dist/compliance/loader.js +124 -0
- package/dist/compliance/types.d.ts +184 -0
- package/dist/compliance/types.js +41 -0
- package/dist/compliance-clearance.d.ts +269 -0
- package/dist/compliance-clearance.js +269 -0
- package/dist/config.js +17 -0
- package/dist/cost-tracker.d.ts +22 -0
- package/dist/cost-tracker.js +41 -0
- package/dist/database/adapters/external.js +5 -1
- package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
- package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
- package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
- package/dist/embedding/consumers/tessellation-drift.js +76 -0
- package/dist/embedding/cross-provider.d.ts +78 -0
- package/dist/embedding/cross-provider.js +75 -0
- package/dist/embedding/deprecation.d.ts +151 -0
- package/dist/embedding/deprecation.js +229 -0
- package/dist/embedding/errors.d.ts +90 -0
- package/dist/embedding/errors.js +150 -0
- package/dist/embedding/index.d.ts +29 -0
- package/dist/embedding/index.js +24 -0
- package/dist/embedding/pipeline-load.d.ts +146 -0
- package/dist/embedding/pipeline-load.js +178 -0
- package/dist/embedding/registry.d.ts +45 -0
- package/dist/embedding/registry.js +61 -0
- package/dist/embedding/stale-vector.d.ts +110 -0
- package/dist/embedding/stale-vector.js +92 -0
- package/dist/embedding/storage/index.d.ts +51 -0
- package/dist/embedding/storage/index.js +43 -0
- package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
- package/dist/embedding/storage/jsonl-backend.js +332 -0
- package/dist/embedding/storage/types.d.ts +135 -0
- package/dist/embedding/storage/types.js +13 -0
- package/dist/embedding/types.d.ts +180 -0
- package/dist/embedding/types.js +10 -0
- package/dist/execute.d.ts +29 -2
- package/dist/execute.js +171 -30
- package/dist/index.d.ts +14 -2
- package/dist/index.js +19 -2
- package/dist/journey/inheritance-validator.d.ts +396 -0
- package/dist/journey/inheritance-validator.js +370 -0
- package/dist/journey/state-id-drift-rule.d.ts +137 -0
- package/dist/journey/state-id-drift-rule.js +245 -0
- package/dist/journey-sa2-router.d.ts +395 -0
- package/dist/journey-sa2-router.js +308 -0
- package/dist/runners/review-agent.js +6 -2
- package/dist/runners/runner-registry.d.ts +36 -0
- package/dist/runners/runner-registry.js +90 -0
- package/dist/runtime/attestations.d.ts +173 -13
- package/dist/runtime/attestations.js +252 -40
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/sa-scoring/layer3-llm.js +6 -1
- package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
- package/dist/sa-scoring/revision-proposal-config.js +198 -0
- package/dist/sa-scoring/revision-proposal.d.ts +285 -0
- package/dist/sa-scoring/revision-proposal.js +417 -0
- package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
- package/dist/signal-ingestion/adapters/community-thread.js +55 -0
- package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
- package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
- package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
- package/dist/signal-ingestion/adapters/manual.js +112 -0
- package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
- package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
- package/dist/signal-ingestion/classifier.d.ts +205 -0
- package/dist/signal-ingestion/classifier.js +494 -0
- package/dist/signal-ingestion/clustering-types.d.ts +36 -0
- package/dist/signal-ingestion/clustering-types.js +14 -0
- package/dist/signal-ingestion/clustering.d.ts +200 -0
- package/dist/signal-ingestion/clustering.js +413 -0
- package/dist/signal-ingestion/config.d.ts +351 -0
- package/dist/signal-ingestion/config.js +587 -0
- package/dist/signal-ingestion/d1.d.ts +252 -0
- package/dist/signal-ingestion/d1.js +235 -0
- package/dist/signal-ingestion/errors.d.ts +73 -0
- package/dist/signal-ingestion/errors.js +108 -0
- package/dist/signal-ingestion/governance-events.d.ts +181 -0
- package/dist/signal-ingestion/governance-events.js +189 -0
- package/dist/signal-ingestion/index.d.ts +35 -0
- package/dist/signal-ingestion/index.js +53 -0
- package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
- package/dist/signal-ingestion/manual-share-metric.js +106 -0
- package/dist/signal-ingestion/registry.d.ts +40 -0
- package/dist/signal-ingestion/registry.js +137 -0
- package/dist/signal-ingestion/residency.d.ts +227 -0
- package/dist/signal-ingestion/residency.js +238 -0
- package/dist/signal-ingestion/significance.d.ts +554 -0
- package/dist/signal-ingestion/significance.js +555 -0
- package/dist/signal-ingestion/types.d.ts +191 -0
- package/dist/signal-ingestion/types.js +8 -0
- package/dist/substrate/drift-composition.d.ts +270 -0
- package/dist/substrate/drift-composition.js +306 -0
- package/dist/substrate/drift-tui-surface.d.ts +61 -0
- package/dist/substrate/drift-tui-surface.js +102 -0
- package/dist/substrate/identity-class.d.ts +176 -0
- package/dist/substrate/identity-class.js +201 -0
- package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
- package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
- package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
- package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
- package/dist/tessellation/rule-registry.d.ts +269 -0
- package/dist/tessellation/rule-registry.js +92 -0
- package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
- package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
- package/dist/tessellation-admission.d.ts +162 -0
- package/dist/tessellation-admission.js +146 -0
- package/dist/tessellation-drift.d.ts +246 -0
- package/dist/tessellation-drift.js +250 -0
- package/dist/validate-config.js +13 -0
- package/dist/validate-issue.js +2 -2
- package/dist/variant/cardinality-activation.d.ts +126 -0
- package/dist/variant/cardinality-activation.js +101 -0
- package/dist/variant/deprecation-lifecycle.d.ts +184 -0
- package/dist/variant/deprecation-lifecycle.js +208 -0
- package/dist/variant/drift-extension.d.ts +136 -0
- package/dist/variant/drift-extension.js +164 -0
- package/dist/variant/engineering-review.d.ts +185 -0
- package/dist/variant/engineering-review.js +142 -0
- package/dist/variant/index.d.ts +32 -0
- package/dist/variant/index.js +32 -0
- package/dist/variant/inheritance-validator.d.ts +165 -0
- package/dist/variant/inheritance-validator.js +139 -0
- package/dist/variant/internal-adopter/index.d.ts +11 -0
- package/dist/variant/internal-adopter/index.js +10 -0
- package/dist/variant/internal-adopter/products.d.ts +156 -0
- package/dist/variant/internal-adopter/products.js +366 -0
- package/dist/variant-admission.d.ts +316 -0
- package/dist/variant-admission.js +247 -0
- package/package.json +10 -8
|
@@ -25,6 +25,18 @@ export interface AdmissionInput {
|
|
|
25
25
|
commentCount: number;
|
|
26
26
|
/** ISO timestamp of issue creation. */
|
|
27
27
|
createdAt: string;
|
|
28
|
+
/**
|
|
29
|
+
* Canonical work item ID for dep-graph lookups (RFC-0014 / RFC-0009 Phase 2.1).
|
|
30
|
+
*
|
|
31
|
+
* When present, used instead of `#${issueNumber}` to match the work item
|
|
32
|
+
* against dep-graph snapshot entries (which use backlog task IDs like
|
|
33
|
+
* `AISDLC-313`). Backlog adapters set this from the task's frontmatter `id`.
|
|
34
|
+
*
|
|
35
|
+
* Absent for GitHub-issue based inputs — the admission composite falls back
|
|
36
|
+
* to `#${issueNumber}` which is consistent with `mapIssueToPriorityInput`'s
|
|
37
|
+
* `itemId` field.
|
|
38
|
+
*/
|
|
39
|
+
workItemId?: string;
|
|
28
40
|
/** GitHub author_association — determines trust-based signal boosting. */
|
|
29
41
|
authorAssociation?: AuthorAssociation;
|
|
30
42
|
/** GitHub login of the issue author (used by C5 principal match). */
|
|
@@ -116,6 +128,24 @@ export interface DesignAuthoritySignal {
|
|
|
116
128
|
* RFC's §14.2 weighting constraint.
|
|
117
129
|
*/
|
|
118
130
|
principalsDeclared?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* RFC-0008 §C5 Source 3 — automated compliance-assessment signal in [-1, 1].
|
|
133
|
+
*
|
|
134
|
+
* Populated by `buildDesignAuthoritySignal` when the resolved DSB carries
|
|
135
|
+
* `status.tokenCompliance.currentCoverage`:
|
|
136
|
+
* >= 0.8 (80 %) → small positive signal (+0.3): design system is healthy here
|
|
137
|
+
* < 0.4 (40 %) → small negative signal (-0.2): design system is fragile here
|
|
138
|
+
* otherwise → 0 (neutral)
|
|
139
|
+
*
|
|
140
|
+
* This signal fires from the DSB health data alone — no principal participation
|
|
141
|
+
* required. It is additive to the principal-participation weight in
|
|
142
|
+
* `computeDesignAuthorityWeight`, so a fully-loaded DSB with high compliance
|
|
143
|
+
* produces `hcDesign > 0` even when no design-authority principal commented.
|
|
144
|
+
*
|
|
145
|
+
* Absent (undefined) when no DSB is resolved or the DSB lacks token-compliance
|
|
146
|
+
* status — callers treat absence the same as 0.
|
|
147
|
+
*/
|
|
148
|
+
complianceSignal?: number;
|
|
119
149
|
}
|
|
120
150
|
export interface AdmissionThresholds {
|
|
121
151
|
minimumScore: number;
|
package/dist/admission-score.js
CHANGED
|
@@ -45,7 +45,10 @@ export function mapIssueToPriorityInput(input) {
|
|
|
45
45
|
const isTrusted = assoc === 'OWNER' || assoc === 'MEMBER' || assoc === 'COLLABORATOR';
|
|
46
46
|
const isContributor = assoc === 'CONTRIBUTOR';
|
|
47
47
|
// ── Complexity from issue body, AC count, or backlog context ─
|
|
48
|
-
|
|
48
|
+
// Horizontal-whitespace classes ([ \t]) around \n+ so the sub-patterns can't
|
|
49
|
+
// overlap (\s includes \n) — avoids polynomial backtracking on attacker-
|
|
50
|
+
// controlled issue bodies (CodeQL js/polynomial-redos).
|
|
51
|
+
const complexityMatch = input.body?.match(/###?[ \t]*Complexity[ \t]*\n+[ \t]*(\d+)/i);
|
|
49
52
|
let complexity = complexityMatch ? Number(complexityMatch[1]) : undefined;
|
|
50
53
|
// Backlog tasks rarely include a `### Complexity` header — fall back to AC count
|
|
51
54
|
// (each AC is roughly one logical unit; clamp at 10).
|
|
@@ -18,6 +18,36 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { PriorityInput, QualityFlag } from '@ai-sdlc/reference';
|
|
20
20
|
import { type AdmissionInput } from './admission-score.js';
|
|
21
|
+
/**
|
|
22
|
+
* Extract a code-area string from a list of file/path references.
|
|
23
|
+
*
|
|
24
|
+
* Algorithm:
|
|
25
|
+
* 1. Filter references down to path-like strings (not URLs, not AISDLC-N
|
|
26
|
+
* IDs, not RFC-NNNN IDs) — the path-like entries are the ones that
|
|
27
|
+
* carry blast-radius signal.
|
|
28
|
+
* 2. Compute the deepest common path prefix across all filtered paths
|
|
29
|
+
* at the directory level (e.g. `pipeline-cli/src/orchestrator/filters/x.ts`
|
|
30
|
+
* + `pipeline-cli/src/orchestrator/loop.ts` → `pipeline-cli/src/orchestrator`).
|
|
31
|
+
* 3. Return the prefix, or `undefined` when there are no path references.
|
|
32
|
+
*
|
|
33
|
+
* The caller uses the result to populate `EnrichmentContext.codeArea` so
|
|
34
|
+
* `buildCodeAreaQuality()` can look up per-area metrics instead of the
|
|
35
|
+
* uniform Eρ variance of 0.30.
|
|
36
|
+
*
|
|
37
|
+
* @param references Raw reference strings from frontmatter `references:` AND
|
|
38
|
+
* any `## References` body section — callers are responsible
|
|
39
|
+
* for merging both sources before calling here.
|
|
40
|
+
*/
|
|
41
|
+
export declare function extractCodeAreaFromReferences(references: string[]): string | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Parse file-path references from a `## References` markdown section.
|
|
44
|
+
*
|
|
45
|
+
* The body format is a bullet list where each item may be a bare path,
|
|
46
|
+
* a backtick-quoted path, a description with an inline path, or a mix.
|
|
47
|
+
* We extract the first backtick-quoted segment or the first slash-
|
|
48
|
+
* containing word from each bullet.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseBodyReferences(body: string): string[];
|
|
21
51
|
export interface BacklogAcceptanceCriterion {
|
|
22
52
|
index: number;
|
|
23
53
|
text: string;
|
|
@@ -38,6 +68,11 @@ export interface BacklogTaskSnapshot {
|
|
|
38
68
|
createdBy?: string;
|
|
39
69
|
acceptanceCriteria: BacklogAcceptanceCriterion[];
|
|
40
70
|
references: string[];
|
|
71
|
+
/**
|
|
72
|
+
* References extracted from the `## References` body section.
|
|
73
|
+
* Merged with frontmatter `references` when computing `codeArea`.
|
|
74
|
+
*/
|
|
75
|
+
bodyReferences: string[];
|
|
41
76
|
/** Task IDs this task is blocked by (frontmatter `dependencies`). */
|
|
42
77
|
dependencies: string[];
|
|
43
78
|
/** Filesystem path the snapshot was read from. */
|
|
@@ -83,6 +118,16 @@ export interface BacklogAdmissionMapping {
|
|
|
83
118
|
priorityInputOverrides: Partial<PriorityInput>;
|
|
84
119
|
/** Quality flags surfaced for renderers (zombie close, etc). */
|
|
85
120
|
qualityFlags: QualityFlag[];
|
|
121
|
+
/**
|
|
122
|
+
* Deepest common path prefix computed from all references in the task
|
|
123
|
+
* (frontmatter `references:` + `## References` body section).
|
|
124
|
+
*
|
|
125
|
+
* Pass to `EnrichmentContext.codeArea` when calling `enrichAdmissionInput()`
|
|
126
|
+
* so `buildCodeAreaQuality()` can look up per-area metrics instead of the
|
|
127
|
+
* uniform Eρ variance of 0.30. Undefined when the task has no file-path
|
|
128
|
+
* references — callers should fall through to the uniform default.
|
|
129
|
+
*/
|
|
130
|
+
codeArea: string | undefined;
|
|
86
131
|
}
|
|
87
132
|
/**
|
|
88
133
|
* Map a Backlog snapshot onto an `AdmissionInput` plus the
|
package/dist/backlog-adapter.js
CHANGED
|
@@ -20,6 +20,139 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
import { parse as parseYaml } from 'yaml';
|
|
22
22
|
import { normalizeBacklogPriority } from './admission-score.js';
|
|
23
|
+
// ── Code-area extraction ────────────────────────────────────────────
|
|
24
|
+
/**
|
|
25
|
+
* Extract a code-area string from a list of file/path references.
|
|
26
|
+
*
|
|
27
|
+
* Algorithm:
|
|
28
|
+
* 1. Filter references down to path-like strings (not URLs, not AISDLC-N
|
|
29
|
+
* IDs, not RFC-NNNN IDs) — the path-like entries are the ones that
|
|
30
|
+
* carry blast-radius signal.
|
|
31
|
+
* 2. Compute the deepest common path prefix across all filtered paths
|
|
32
|
+
* at the directory level (e.g. `pipeline-cli/src/orchestrator/filters/x.ts`
|
|
33
|
+
* + `pipeline-cli/src/orchestrator/loop.ts` → `pipeline-cli/src/orchestrator`).
|
|
34
|
+
* 3. Return the prefix, or `undefined` when there are no path references.
|
|
35
|
+
*
|
|
36
|
+
* The caller uses the result to populate `EnrichmentContext.codeArea` so
|
|
37
|
+
* `buildCodeAreaQuality()` can look up per-area metrics instead of the
|
|
38
|
+
* uniform Eρ variance of 0.30.
|
|
39
|
+
*
|
|
40
|
+
* @param references Raw reference strings from frontmatter `references:` AND
|
|
41
|
+
* any `## References` body section — callers are responsible
|
|
42
|
+
* for merging both sources before calling here.
|
|
43
|
+
*/
|
|
44
|
+
export function extractCodeAreaFromReferences(references) {
|
|
45
|
+
// Strip leading/trailing backtick pairs, parentheses, brackets and whitespace
|
|
46
|
+
// (common in `## References` bullet items like `` `path/to/file.ts` ``).
|
|
47
|
+
const cleaned = references.map((r) => r
|
|
48
|
+
.replace(/^[`(['"]*/, '')
|
|
49
|
+
.replace(/[`)\]'"]*$/, '')
|
|
50
|
+
.trim());
|
|
51
|
+
// Keep only path-like references. Exclude:
|
|
52
|
+
// - URLs (http://, https://, ...)
|
|
53
|
+
// - AISDLC-N IDs
|
|
54
|
+
// - RFC-NNNN IDs
|
|
55
|
+
// - Plain words with no path separator
|
|
56
|
+
// - Empty strings
|
|
57
|
+
const pathRefs = cleaned.filter((r) => {
|
|
58
|
+
if (!r)
|
|
59
|
+
return false;
|
|
60
|
+
if (/^https?:\/\//i.test(r))
|
|
61
|
+
return false;
|
|
62
|
+
if (/^aisdlc-\d+$/i.test(r))
|
|
63
|
+
return false;
|
|
64
|
+
if (/^rfc-\d{4}/i.test(r))
|
|
65
|
+
return false;
|
|
66
|
+
// Must contain at least one path separator to be treated as a file path.
|
|
67
|
+
// Also accept paths that look like `dir/file.ext` with an extension.
|
|
68
|
+
return r.includes('/');
|
|
69
|
+
});
|
|
70
|
+
if (pathRefs.length === 0)
|
|
71
|
+
return undefined;
|
|
72
|
+
if (pathRefs.length === 1) {
|
|
73
|
+
return pathPrefix(pathRefs[0]);
|
|
74
|
+
}
|
|
75
|
+
// Compute the deepest common directory prefix.
|
|
76
|
+
const segments = pathRefs.map((r) => pathPrefix(r).split('/'));
|
|
77
|
+
let common = segments[0];
|
|
78
|
+
for (let i = 1; i < segments.length; i++) {
|
|
79
|
+
common = commonPrefix(common, segments[i]);
|
|
80
|
+
}
|
|
81
|
+
if (common.length === 0)
|
|
82
|
+
return undefined;
|
|
83
|
+
return common.join('/');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Return the directory portion of a path reference.
|
|
87
|
+
* `pipeline-cli/src/foo.ts` → `pipeline-cli/src`
|
|
88
|
+
* `pipeline-cli/src/orchestrator/` → `pipeline-cli/src/orchestrator`
|
|
89
|
+
* `pipeline-cli/src/orchestrator` → `pipeline-cli/src` (no trailing slash → treat as file)
|
|
90
|
+
*/
|
|
91
|
+
function pathPrefix(ref) {
|
|
92
|
+
const stripped = ref.replace(/\/$/, ''); // remove trailing slash
|
|
93
|
+
const lastSlash = stripped.lastIndexOf('/');
|
|
94
|
+
// If the last segment looks like a file (has an extension) or the path has a
|
|
95
|
+
// trailing slash (already a directory reference), take the parent. Otherwise
|
|
96
|
+
// treat the whole thing as a directory.
|
|
97
|
+
const lastSegment = lastSlash >= 0 ? stripped.slice(lastSlash + 1) : stripped;
|
|
98
|
+
if (lastSlash < 0)
|
|
99
|
+
return stripped; // bare filename with no directory
|
|
100
|
+
// Has a dot in the last segment AND it's not a hidden dir like `.ai-sdlc` → file
|
|
101
|
+
if (lastSegment.includes('.') && !lastSegment.startsWith('.')) {
|
|
102
|
+
return stripped.slice(0, lastSlash);
|
|
103
|
+
}
|
|
104
|
+
return stripped; // already a directory path
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Longest common prefix of two string arrays (segment-by-segment).
|
|
108
|
+
*/
|
|
109
|
+
function commonPrefix(a, b) {
|
|
110
|
+
const result = [];
|
|
111
|
+
const len = Math.min(a.length, b.length);
|
|
112
|
+
for (let i = 0; i < len; i++) {
|
|
113
|
+
if (a[i] !== b[i])
|
|
114
|
+
break;
|
|
115
|
+
result.push(a[i]);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Parse file-path references from a `## References` markdown section.
|
|
121
|
+
*
|
|
122
|
+
* The body format is a bullet list where each item may be a bare path,
|
|
123
|
+
* a backtick-quoted path, a description with an inline path, or a mix.
|
|
124
|
+
* We extract the first backtick-quoted segment or the first slash-
|
|
125
|
+
* containing word from each bullet.
|
|
126
|
+
*/
|
|
127
|
+
export function parseBodyReferences(body) {
|
|
128
|
+
// Split the body by `## ` headings (preserving each section in its entirety)
|
|
129
|
+
// then find the `## References` section specifically. This avoids regex
|
|
130
|
+
// backtracking issues with greedy vs lazy quantifiers when the body contains
|
|
131
|
+
// multiple `## ` headings.
|
|
132
|
+
const sections = body.split(/\n(?=##\s)/);
|
|
133
|
+
const section = sections.find((s) => /^##\s+References\b/i.test(s));
|
|
134
|
+
if (!section)
|
|
135
|
+
return [];
|
|
136
|
+
const out = [];
|
|
137
|
+
for (const line of section.split(/\r?\n/)) {
|
|
138
|
+
// Must be a bullet item.
|
|
139
|
+
if (!/^\s*-\s/.test(line))
|
|
140
|
+
continue;
|
|
141
|
+
const content = line.replace(/^\s*-\s+/, '');
|
|
142
|
+
// Prefer backtick-quoted path.
|
|
143
|
+
const btMatch = content.match(/`([^`]+)`/);
|
|
144
|
+
if (btMatch) {
|
|
145
|
+
out.push(btMatch[1]);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Fall back to the first slash-containing token.
|
|
149
|
+
const tokens = content.split(/\s+/);
|
|
150
|
+
const pathToken = tokens.find((t) => t.includes('/'));
|
|
151
|
+
if (pathToken)
|
|
152
|
+
out.push(pathToken);
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
23
156
|
// ── Parser ──────────────────────────────────────────────────────────
|
|
24
157
|
/**
|
|
25
158
|
* Parse a Backlog.md task markdown file into `BacklogTaskSnapshot`.
|
|
@@ -38,6 +171,10 @@ export function parseBacklogTask(content, sourcePath) {
|
|
|
38
171
|
const dependencies = normaliseStringArray(fm.dependencies);
|
|
39
172
|
const description = extractSection(content, 'Description');
|
|
40
173
|
const acceptanceCriteria = extractAcceptanceCriteria(content);
|
|
174
|
+
// Strip frontmatter block before parsing body sections (the `---` block
|
|
175
|
+
// can contain `## References`-like content that would confuse body parsing).
|
|
176
|
+
const bodyOnly = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
|
|
177
|
+
const bodyReferences = parseBodyReferences(bodyOnly);
|
|
41
178
|
return {
|
|
42
179
|
id,
|
|
43
180
|
numericId,
|
|
@@ -51,6 +188,7 @@ export function parseBacklogTask(content, sourcePath) {
|
|
|
51
188
|
createdBy: fm.created_by ? String(fm.created_by).trim() : undefined,
|
|
52
189
|
acceptanceCriteria,
|
|
53
190
|
references,
|
|
191
|
+
bodyReferences,
|
|
54
192
|
dependencies,
|
|
55
193
|
sourcePath,
|
|
56
194
|
};
|
|
@@ -308,6 +446,12 @@ export function mapBacklogTaskToAdmissionInput(snap, options = {}) {
|
|
|
308
446
|
status: snap.status,
|
|
309
447
|
},
|
|
310
448
|
};
|
|
449
|
+
// ── Code-area extraction ─────────────────────────────────────────
|
|
450
|
+
// Merge frontmatter references + body references and compute the deepest
|
|
451
|
+
// common path prefix. The result is forwarded to EnrichmentContext.codeArea
|
|
452
|
+
// so admission scoring can use per-area Eρ variance instead of 0.30.
|
|
453
|
+
const allReferences = [...snap.references, ...snap.bodyReferences];
|
|
454
|
+
const codeArea = extractCodeAreaFromReferences(allReferences);
|
|
311
455
|
const priorityInputOverrides = {};
|
|
312
456
|
if (soulAlignment !== 0.5)
|
|
313
457
|
priorityInputOverrides.soulAlignment = soulAlignment;
|
|
@@ -329,7 +473,7 @@ export function mapBacklogTaskToAdmissionInput(snap, options = {}) {
|
|
|
329
473
|
priorityInputOverrides.defectRiskFactor = defectRiskFactor;
|
|
330
474
|
if (qualityFlags.length > 0)
|
|
331
475
|
priorityInputOverrides.qualityFlags = qualityFlags;
|
|
332
|
-
return { input, priorityInputOverrides, qualityFlags };
|
|
476
|
+
return { input, priorityInputOverrides, qualityFlags, codeArea };
|
|
333
477
|
}
|
|
334
478
|
function buildAdmissionBody(snap, complexity) {
|
|
335
479
|
const parts = [];
|
package/dist/calibration.d.ts
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Per v1.1 note: this adjusts the multiplicative Cκ term, NOT SA-2
|
|
12
12
|
* directly. Per-dimension calibration lands in PPA v1.1 §17.
|
|
13
|
+
*
|
|
14
|
+
* RFC-0009 Phase 2.2 extension: `buildSoulCalibrationMatrix` aggregates
|
|
15
|
+
* Cκ coefficients per-soul × per-dimension (N×M cells). Feedback events
|
|
16
|
+
* tagged with a soul slug as their `category` and a SA dimension drive
|
|
17
|
+
* the matrix cells. Souls × dimensions with insufficient data are omitted
|
|
18
|
+
* from the matrix (callers fall back to the scalar coefficient or 1.0).
|
|
13
19
|
*/
|
|
14
20
|
import type { SaDimension } from './state/types.js';
|
|
15
21
|
import type { SAFeedbackStore } from './sa-scoring/feedback-store.js';
|
|
@@ -40,4 +46,79 @@ export interface BuildCategoryCoefficientsInput {
|
|
|
40
46
|
* scalar fallback applies to them.
|
|
41
47
|
*/
|
|
42
48
|
export declare function buildCategoryCoefficients(feedback: SAFeedbackStore, input?: BuildCategoryCoefficientsInput): Record<string, number>;
|
|
49
|
+
/**
|
|
50
|
+
* Input options for `buildSoulCalibrationMatrix`.
|
|
51
|
+
*/
|
|
52
|
+
export interface BuildSoulCalibrationMatrixInput {
|
|
53
|
+
/**
|
|
54
|
+
* Soul slugs to include in the matrix (N axis).
|
|
55
|
+
* Feedback events whose `category` matches a slug are collected.
|
|
56
|
+
*/
|
|
57
|
+
souls: readonly string[];
|
|
58
|
+
/**
|
|
59
|
+
* SA dimensions to include in the matrix (M axis).
|
|
60
|
+
* When absent, all SA dimensions are sampled.
|
|
61
|
+
*/
|
|
62
|
+
dimensions?: readonly SaDimension[];
|
|
63
|
+
/** Trailing-window filter (ISO timestamp). */
|
|
64
|
+
since?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Minimum feedback event count per (soul, dimension) cell before a
|
|
67
|
+
* coefficient is emitted. Cells below this threshold are omitted from
|
|
68
|
+
* the matrix — callers should fall back to 1.0 (neutral) for absent cells.
|
|
69
|
+
*/
|
|
70
|
+
minSampleSize?: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The N×M Cκ calibration matrix for a tessellated platform.
|
|
74
|
+
*
|
|
75
|
+
* cells[soulSlug][dimension] = calibration coefficient in [0.7, 1.3]
|
|
76
|
+
*
|
|
77
|
+
* Cells omitted from the map have insufficient feedback data.
|
|
78
|
+
* Callers treat absent cells as 1.0 (neutral, no calibration adjustment).
|
|
79
|
+
*/
|
|
80
|
+
export interface SoulCalibrationMatrix {
|
|
81
|
+
/**
|
|
82
|
+
* N×M map: `{ soulSlug: { saDimension: coefficient } }`.
|
|
83
|
+
* Only cells with sufficient data are present.
|
|
84
|
+
*/
|
|
85
|
+
cells: Record<string, Record<string, number>>;
|
|
86
|
+
/**
|
|
87
|
+
* Souls (N axis) included in this matrix — equal to `input.souls`.
|
|
88
|
+
* Useful for distinguishing "soul has data but coefficient is neutral"
|
|
89
|
+
* from "soul was not included in the query".
|
|
90
|
+
*/
|
|
91
|
+
souls: readonly string[];
|
|
92
|
+
/**
|
|
93
|
+
* SA dimensions (M axis) sampled — equal to `input.dimensions` when
|
|
94
|
+
* provided, or all dimensions found in the feedback window.
|
|
95
|
+
*/
|
|
96
|
+
dimensions: readonly string[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Aggregate Cκ calibration coefficients per-soul × per-dimension (N×M cells).
|
|
100
|
+
*
|
|
101
|
+
* Feedback events tagged with:
|
|
102
|
+
* - `category = <soul-slug>` (identifies which soul the feedback is for)
|
|
103
|
+
* - `dimension = <SA-1 | SA-2>` (SA dimension the feedback applies to)
|
|
104
|
+
*
|
|
105
|
+
* ...drive the per-cell coefficient using the same formula as
|
|
106
|
+
* `computeCalibrationCoefficient`.
|
|
107
|
+
*
|
|
108
|
+
* Usage in tessellated admission scoring (RFC-0009 §6):
|
|
109
|
+
* - Look up `matrix.cells[targetSoul][dimension]` for the effective Cκ
|
|
110
|
+
* - Fall back to 1.0 (neutral) when the cell is absent (insufficient data)
|
|
111
|
+
* - Cross-soul aggregate: apply `crossSoulScoringRule` over per-soul cells
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const matrix = buildSoulCalibrationMatrix(feedback, {
|
|
116
|
+
* souls: ['soul-a', 'soul-b', 'soul-c'],
|
|
117
|
+
* dimensions: ['SA-1', 'SA-2'],
|
|
118
|
+
* minSampleSize: 5,
|
|
119
|
+
* });
|
|
120
|
+
* const ckSoulA = matrix.cells['soul-a']?.['SA-1'] ?? 1.0;
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export declare function buildSoulCalibrationMatrix(feedback: SAFeedbackStore, input: BuildSoulCalibrationMatrixInput): SoulCalibrationMatrix;
|
|
43
124
|
//# sourceMappingURL=calibration.d.ts.map
|
package/dist/calibration.js
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Per v1.1 note: this adjusts the multiplicative Cκ term, NOT SA-2
|
|
12
12
|
* directly. Per-dimension calibration lands in PPA v1.1 §17.
|
|
13
|
+
*
|
|
14
|
+
* RFC-0009 Phase 2.2 extension: `buildSoulCalibrationMatrix` aggregates
|
|
15
|
+
* Cκ coefficients per-soul × per-dimension (N×M cells). Feedback events
|
|
16
|
+
* tagged with a soul slug as their `category` and a SA dimension drive
|
|
17
|
+
* the matrix cells. Souls × dimensions with insufficient data are omitted
|
|
18
|
+
* from the matrix (callers fall back to the scalar coefficient or 1.0).
|
|
13
19
|
*/
|
|
14
20
|
export const CALIBRATION_MIN = 0.7;
|
|
15
21
|
export const CALIBRATION_MAX = 1.3;
|
|
@@ -73,4 +79,74 @@ export function buildCategoryCoefficients(feedback, input = {}) {
|
|
|
73
79
|
}
|
|
74
80
|
return result;
|
|
75
81
|
}
|
|
82
|
+
/** All recognized SA dimensions for default matrix columns. */
|
|
83
|
+
const ALL_SA_DIMENSIONS = ['SA-1', 'SA-2'];
|
|
84
|
+
/**
|
|
85
|
+
* Aggregate Cκ calibration coefficients per-soul × per-dimension (N×M cells).
|
|
86
|
+
*
|
|
87
|
+
* Feedback events tagged with:
|
|
88
|
+
* - `category = <soul-slug>` (identifies which soul the feedback is for)
|
|
89
|
+
* - `dimension = <SA-1 | SA-2>` (SA dimension the feedback applies to)
|
|
90
|
+
*
|
|
91
|
+
* ...drive the per-cell coefficient using the same formula as
|
|
92
|
+
* `computeCalibrationCoefficient`.
|
|
93
|
+
*
|
|
94
|
+
* Usage in tessellated admission scoring (RFC-0009 §6):
|
|
95
|
+
* - Look up `matrix.cells[targetSoul][dimension]` for the effective Cκ
|
|
96
|
+
* - Fall back to 1.0 (neutral) when the cell is absent (insufficient data)
|
|
97
|
+
* - Cross-soul aggregate: apply `crossSoulScoringRule` over per-soul cells
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* const matrix = buildSoulCalibrationMatrix(feedback, {
|
|
102
|
+
* souls: ['soul-a', 'soul-b', 'soul-c'],
|
|
103
|
+
* dimensions: ['SA-1', 'SA-2'],
|
|
104
|
+
* minSampleSize: 5,
|
|
105
|
+
* });
|
|
106
|
+
* const ckSoulA = matrix.cells['soul-a']?.['SA-1'] ?? 1.0;
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function buildSoulCalibrationMatrix(feedback, input) {
|
|
110
|
+
const dims = input.dimensions ?? ALL_SA_DIMENSIONS;
|
|
111
|
+
const cells = {};
|
|
112
|
+
for (const soul of input.souls) {
|
|
113
|
+
const soulCells = {};
|
|
114
|
+
for (const dim of dims) {
|
|
115
|
+
const window = { dimension: dim, since: input.since };
|
|
116
|
+
const events = feedback.list(window).filter((e) => e.category === soul);
|
|
117
|
+
if (events.length === 0)
|
|
118
|
+
continue;
|
|
119
|
+
const bucket = {
|
|
120
|
+
accepts: 0,
|
|
121
|
+
dismisses: 0,
|
|
122
|
+
escalates: 0,
|
|
123
|
+
overrides: 0,
|
|
124
|
+
};
|
|
125
|
+
for (const e of events) {
|
|
126
|
+
switch (e.signal) {
|
|
127
|
+
case 'accept':
|
|
128
|
+
bucket.accepts++;
|
|
129
|
+
break;
|
|
130
|
+
case 'dismiss':
|
|
131
|
+
bucket.dismisses++;
|
|
132
|
+
break;
|
|
133
|
+
case 'escalate':
|
|
134
|
+
bucket.escalates++;
|
|
135
|
+
break;
|
|
136
|
+
case 'override':
|
|
137
|
+
bucket.overrides = (bucket.overrides ?? 0) + 1;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const sampleSize = bucket.accepts + bucket.dismisses + bucket.escalates;
|
|
142
|
+
if (sampleSize < (input.minSampleSize ?? 1))
|
|
143
|
+
continue;
|
|
144
|
+
soulCells[dim] = computeCalibrationCoefficient(bucket);
|
|
145
|
+
}
|
|
146
|
+
if (Object.keys(soulCells).length > 0) {
|
|
147
|
+
cells[soul] = soulCells;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { cells, souls: input.souls, dimensions: dims };
|
|
151
|
+
}
|
|
76
152
|
//# sourceMappingURL=calibration.js.map
|