@ai-sdlc/orchestrator 0.6.0 → 0.10.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/__test-helpers/git-env.d.ts +40 -0
- package/dist/__test-helpers/git-env.js +73 -0
- package/dist/admission-composite.d.ts +89 -0
- package/dist/admission-composite.js +241 -0
- package/dist/admission-enrichment.d.ts +142 -0
- package/dist/admission-enrichment.js +341 -0
- package/dist/admission-hc.d.ts +81 -0
- package/dist/admission-hc.js +94 -0
- package/dist/admission-score.d.ts +109 -5
- package/dist/admission-score.js +90 -11
- package/dist/analysis/analyzer.js +3 -2
- package/dist/analysis/convention-detector.d.ts +85 -2
- package/dist/analysis/convention-detector.js +375 -70
- package/dist/analysis/diff-analyzer.d.ts +33 -0
- package/dist/analysis/diff-analyzer.js +122 -0
- package/dist/analysis/hotspot-analyzer.js +3 -1
- package/dist/analysis/index.d.ts +2 -1
- package/dist/analysis/index.js +2 -1
- package/dist/artifacts/index.d.ts +65 -0
- package/dist/artifacts/index.js +142 -0
- package/dist/backlog-adapter.d.ts +121 -0
- package/dist/backlog-adapter.js +438 -0
- package/dist/calibration.d.ts +43 -0
- package/dist/calibration.js +76 -0
- package/dist/cli/commands/agents.d.ts +24 -0
- package/dist/cli/commands/agents.js +66 -1
- package/dist/cli/commands/git-remote.d.ts +74 -0
- package/dist/cli/commands/git-remote.js +170 -0
- package/dist/cli/commands/health.d.ts +4 -0
- package/dist/cli/commands/health.js +13 -1
- package/dist/cli/commands/init-features.d.ts +208 -0
- package/dist/cli/commands/init-features.js +473 -0
- package/dist/cli/commands/init-templates.d.ts +104 -0
- package/dist/cli/commands/init-templates.js +399 -0
- package/dist/cli/commands/init.d.ts +48 -0
- package/dist/cli/commands/init.js +322 -23
- package/dist/cli/commands/mcp-setup.d.ts +31 -0
- package/dist/cli/commands/mcp-setup.js +78 -8
- package/dist/cli/formatters/table.js +15 -2
- package/dist/cli/index.d.ts +14 -1
- package/dist/cli/index.js +81 -20
- package/dist/cli/versions.d.ts +57 -0
- package/dist/cli/versions.js +128 -0
- package/dist/code-area-classifier.d.ts +21 -0
- package/dist/code-area-classifier.js +48 -0
- package/dist/config.d.ts +33 -1
- package/dist/config.js +78 -8
- package/dist/database/adapters/external.d.ts +24 -0
- package/dist/database/adapters/external.js +80 -0
- package/dist/database/adapters/neon.d.ts +41 -0
- package/dist/database/adapters/neon.js +98 -0
- package/dist/database/adapters/pg-snapshot-restore.d.ts +28 -0
- package/dist/database/adapters/pg-snapshot-restore.js +68 -0
- package/dist/database/adapters/sqlite-copy.d.ts +32 -0
- package/dist/database/adapters/sqlite-copy.js +145 -0
- package/dist/database/connection-injection.d.ts +35 -0
- package/dist/database/connection-injection.js +93 -0
- package/dist/database/index.d.ts +12 -0
- package/dist/database/index.js +23 -0
- package/dist/database/registry.d.ts +13 -0
- package/dist/database/registry.js +27 -0
- package/dist/database/topology.d.ts +52 -0
- package/dist/database/topology.js +44 -0
- package/dist/database/types.d.ts +89 -0
- package/dist/database/types.js +26 -0
- package/dist/design-authority.d.ts +53 -0
- package/dist/design-authority.js +84 -0
- package/dist/design-lookahead.d.ts +64 -0
- package/dist/design-lookahead.js +86 -0
- package/dist/design-quality-trend.d.ts +87 -0
- package/dist/design-quality-trend.js +190 -0
- package/dist/design-system-context.d.ts +46 -0
- package/dist/design-system-context.js +80 -0
- package/dist/design-system-correction-loop.d.ts +64 -0
- package/dist/design-system-correction-loop.js +128 -0
- package/dist/design-system-metrics.d.ts +61 -0
- package/dist/design-system-metrics.js +104 -0
- package/dist/design-system-stewardship.d.ts +22 -0
- package/dist/design-system-stewardship.js +85 -0
- package/dist/design-system-validation.d.ts +37 -0
- package/dist/design-system-validation.js +88 -0
- package/dist/dispatch/index.d.ts +4 -0
- package/dist/dispatch/index.js +4 -0
- package/dist/dispatch/merge-gate.d.ts +46 -0
- package/dist/dispatch/merge-gate.js +90 -0
- package/dist/dispatch/requeue.d.ts +57 -0
- package/dist/dispatch/requeue.js +131 -0
- package/dist/dispatch/worker-pool.d.ts +62 -0
- package/dist/dispatch/worker-pool.js +60 -0
- package/dist/execute.d.ts +40 -0
- package/dist/execute.js +188 -15
- package/dist/fix-ci.js +8 -1
- package/dist/fix-review.js +8 -1
- package/dist/harness/adapters/claude-code.d.ts +29 -0
- package/dist/harness/adapters/claude-code.js +191 -0
- package/dist/harness/adapters/codex.d.ts +25 -0
- package/dist/harness/adapters/codex.js +61 -0
- package/dist/harness/independence.d.ts +51 -0
- package/dist/harness/independence.js +67 -0
- package/dist/harness/index.d.ts +14 -0
- package/dist/harness/index.js +20 -0
- package/dist/harness/registry.d.ts +17 -0
- package/dist/harness/registry.js +31 -0
- package/dist/harness/types.d.ts +123 -0
- package/dist/harness/types.js +8 -0
- package/dist/harness/version-probe.d.ts +14 -0
- package/dist/harness/version-probe.js +123 -0
- package/dist/index.d.ts +30 -4
- package/dist/index.js +35 -2
- package/dist/models/classifier.d.ts +78 -0
- package/dist/models/classifier.js +277 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/index.js +3 -0
- package/dist/models/registry.d.ts +97 -0
- package/dist/models/registry.js +173 -0
- package/dist/pillar-breakdown.d.ts +85 -0
- package/dist/pillar-breakdown.js +162 -0
- package/dist/priority.d.ts +5 -0
- package/dist/priority.js +18 -5
- package/dist/review-meta.d.ts +65 -0
- package/dist/review-meta.js +149 -0
- package/dist/review.d.ts +4 -0
- package/dist/review.js +12 -2
- package/dist/runners/claude-code-sdk.d.ts +31 -0
- package/dist/runners/claude-code-sdk.js +238 -0
- package/dist/runners/claude-code.d.ts +3 -0
- package/dist/runners/claude-code.js +30 -66
- package/dist/runners/codex.js +4 -1
- package/dist/runners/copilot.js +4 -1
- package/dist/runners/cursor.js +4 -1
- package/dist/runners/git-utils.d.ts +81 -0
- package/dist/runners/git-utils.js +201 -0
- package/dist/runners/index.d.ts +3 -0
- package/dist/runners/index.js +3 -0
- package/dist/runners/review-agent.d.ts +20 -0
- package/dist/runners/review-agent.js +171 -41
- package/dist/runners/runner-registry.js +10 -0
- package/dist/runners/sdk-review-runner.d.ts +65 -0
- package/dist/runners/sdk-review-runner.js +185 -0
- package/dist/runners/security-triage.d.ts +20 -4
- package/dist/runners/security-triage.js +39 -15
- package/dist/runners/types.d.ts +6 -0
- package/dist/runtime/attestations.d.ts +766 -0
- package/dist/runtime/attestations.js +1195 -0
- package/dist/runtime/git-env.d.ts +53 -0
- package/dist/runtime/git-env.js +60 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/parallelism-flag.d.ts +28 -0
- package/dist/runtime/parallelism-flag.js +39 -0
- package/dist/runtime/port-allocator.d.ts +32 -0
- package/dist/runtime/port-allocator.js +96 -0
- package/dist/runtime/worktree-pool.d.ts +86 -0
- package/dist/runtime/worktree-pool.js +204 -0
- package/dist/runtime/worktree.d.ts +25 -0
- package/dist/runtime/worktree.js +111 -0
- package/dist/sa-scoring/auto-calibrate.d.ts +69 -0
- package/dist/sa-scoring/auto-calibrate.js +107 -0
- package/dist/sa-scoring/c1-sa2-computable.d.ts +42 -0
- package/dist/sa-scoring/c1-sa2-computable.js +59 -0
- package/dist/sa-scoring/composite.d.ts +107 -0
- package/dist/sa-scoring/composite.js +139 -0
- package/dist/sa-scoring/depparse-client.d.ts +79 -0
- package/dist/sa-scoring/depparse-client.js +187 -0
- package/dist/sa-scoring/did-compiler.d.ts +122 -0
- package/dist/sa-scoring/did-compiler.js +286 -0
- package/dist/sa-scoring/drift-monitor.d.ts +84 -0
- package/dist/sa-scoring/drift-monitor.js +186 -0
- package/dist/sa-scoring/exemplar-bank.d.ts +78 -0
- package/dist/sa-scoring/exemplar-bank.js +154 -0
- package/dist/sa-scoring/feedback-store.d.ts +100 -0
- package/dist/sa-scoring/feedback-store.js +156 -0
- package/dist/sa-scoring/index.d.ts +71 -0
- package/dist/sa-scoring/index.js +158 -0
- package/dist/sa-scoring/layer1-deterministic.d.ts +115 -0
- package/dist/sa-scoring/layer1-deterministic.js +298 -0
- package/dist/sa-scoring/layer2-structural.d.ts +71 -0
- package/dist/sa-scoring/layer2-structural.js +151 -0
- package/dist/sa-scoring/layer3-llm.d.ts +86 -0
- package/dist/sa-scoring/layer3-llm.js +282 -0
- package/dist/sa-scoring/rescore-orchestrator.d.ts +52 -0
- package/dist/sa-scoring/rescore-orchestrator.js +47 -0
- package/dist/scheduling/burn-down.d.ts +27 -0
- package/dist/scheduling/burn-down.js +43 -0
- package/dist/scheduling/calibration.d.ts +42 -0
- package/dist/scheduling/calibration.js +150 -0
- package/dist/scheduling/index.d.ts +8 -0
- package/dist/scheduling/index.js +8 -0
- package/dist/scheduling/ledger.d.ts +59 -0
- package/dist/scheduling/ledger.js +216 -0
- package/dist/scheduling/off-peak.d.ts +27 -0
- package/dist/scheduling/off-peak.js +112 -0
- package/dist/scheduling/schedule-decision.d.ts +39 -0
- package/dist/scheduling/schedule-decision.js +89 -0
- package/dist/scheduling/tier-analysis.d.ts +47 -0
- package/dist/scheduling/tier-analysis.js +81 -0
- package/dist/scheduling/types.d.ts +140 -0
- package/dist/scheduling/types.js +11 -0
- package/dist/shared.d.ts +13 -0
- package/dist/shared.js +32 -0
- package/dist/state/schema.d.ts +5 -1
- package/dist/state/schema.js +223 -1
- package/dist/state/store.d.ts +55 -1
- package/dist/state/store.js +342 -7
- package/dist/state/types.d.ts +139 -0
- package/dist/types.d.ts +1 -1
- package/dist/validate-agent-output.js +4 -1
- package/dist/watch.js +6 -0
- package/package.json +3 -2
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SA Drift Monitor (RFC-0008 Addendum B §B.9.2).
|
|
3
|
+
*
|
|
4
|
+
* Periodically inspects `did_scoring_events` and triggers a
|
|
5
|
+
* `SoulDriftDetected` event when SA-1 or SA-2 rolling statistics
|
|
6
|
+
* indicate the scorer is drifting. Separately reports structural vs
|
|
7
|
+
* LLM means so operators can tell whether the drift is coming from
|
|
8
|
+
* the product (DID review needed) or the LLM layer (exemplar bank
|
|
9
|
+
* recalibration needed).
|
|
10
|
+
*
|
|
11
|
+
* Trigger (per dimension):
|
|
12
|
+
* mean < 0.4 OR stddev > 0.15 — for 3 consecutive windows
|
|
13
|
+
*
|
|
14
|
+
* Hysteresis: caller supplies `lastTriggerAt`; the monitor won't fire
|
|
15
|
+
* again within `recoveryMs` (default 7d) of the previous emission.
|
|
16
|
+
*/
|
|
17
|
+
import type { DesignSystemBinding } from '@ai-sdlc/reference';
|
|
18
|
+
import type { StateStore } from '../state/store.js';
|
|
19
|
+
import type { DidScoringEventRecord, SaDimension } from '../state/types.js';
|
|
20
|
+
export declare const DEFAULT_MEAN_THRESHOLD = 0.4;
|
|
21
|
+
export declare const DEFAULT_STDDEV_THRESHOLD = 0.15;
|
|
22
|
+
/** Number of consecutive windows that must violate to trigger. */
|
|
23
|
+
export declare const DEFAULT_CONSECUTIVE_WINDOWS = 3;
|
|
24
|
+
export declare const DEFAULT_WINDOW_DAYS = 30;
|
|
25
|
+
export declare const DEFAULT_RECOVERY_MS: number;
|
|
26
|
+
export interface WindowStats {
|
|
27
|
+
count: number;
|
|
28
|
+
mean: number;
|
|
29
|
+
stddev: number;
|
|
30
|
+
structuralMean: number;
|
|
31
|
+
llmMean: number;
|
|
32
|
+
deterministicFlags: number;
|
|
33
|
+
/** Inclusive window range — [start, end) in ms. */
|
|
34
|
+
windowStartMs: number;
|
|
35
|
+
windowEndMs: number;
|
|
36
|
+
/** True if this window violated the drift threshold. */
|
|
37
|
+
violates: boolean;
|
|
38
|
+
}
|
|
39
|
+
export type DriftTrend = 'increasing' | 'decreasing' | 'stable';
|
|
40
|
+
export interface SoulDriftDetectedEvent {
|
|
41
|
+
type: 'SoulDriftDetected';
|
|
42
|
+
dimension: SaDimension;
|
|
43
|
+
rollingMean: number;
|
|
44
|
+
rollingStdDev: number;
|
|
45
|
+
sprintsInViolation: number;
|
|
46
|
+
trend: DriftTrend;
|
|
47
|
+
driftSource: {
|
|
48
|
+
deterministicFlags: number;
|
|
49
|
+
structuralScoreMean: number;
|
|
50
|
+
llmScoreMean: number;
|
|
51
|
+
note: string;
|
|
52
|
+
};
|
|
53
|
+
notifiedPrincipals: string[];
|
|
54
|
+
triggeredAt: string;
|
|
55
|
+
}
|
|
56
|
+
export declare function mean(values: readonly number[]): number;
|
|
57
|
+
export declare function stddev(values: readonly number[]): number;
|
|
58
|
+
export declare function computeWindowStats(events: readonly DidScoringEventRecord[], windowStartMs: number, windowEndMs: number, meanThreshold: number, stddevThreshold: number): WindowStats;
|
|
59
|
+
export declare function computeTrend(windows: readonly WindowStats[]): DriftTrend;
|
|
60
|
+
export declare function describeDriftSource(windows: readonly WindowStats[]): {
|
|
61
|
+
structuralScoreMean: number;
|
|
62
|
+
llmScoreMean: number;
|
|
63
|
+
note: string;
|
|
64
|
+
deterministicFlags: number;
|
|
65
|
+
};
|
|
66
|
+
export interface DriftDetectorDeps {
|
|
67
|
+
stateStore: StateStore;
|
|
68
|
+
/** Resolve the DSB that owns this DID (for notification principals). */
|
|
69
|
+
getBinding?: (dimension: SaDimension) => DesignSystemBinding | undefined;
|
|
70
|
+
/** Previous trigger timestamp (for hysteresis). */
|
|
71
|
+
getLastTriggerAt?: (dimension: SaDimension) => string | undefined;
|
|
72
|
+
/** Clock injection for tests. */
|
|
73
|
+
now?: () => number;
|
|
74
|
+
/** Configuration overrides. */
|
|
75
|
+
config?: Partial<{
|
|
76
|
+
meanThreshold: number;
|
|
77
|
+
stddevThreshold: number;
|
|
78
|
+
consecutiveWindows: number;
|
|
79
|
+
windowDays: number;
|
|
80
|
+
recoveryMs: number;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
export declare function detectSoulDrift(dimension: SaDimension, deps: DriftDetectorDeps): SoulDriftDetectedEvent | undefined;
|
|
84
|
+
//# sourceMappingURL=drift-monitor.d.ts.map
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SA Drift Monitor (RFC-0008 Addendum B §B.9.2).
|
|
3
|
+
*
|
|
4
|
+
* Periodically inspects `did_scoring_events` and triggers a
|
|
5
|
+
* `SoulDriftDetected` event when SA-1 or SA-2 rolling statistics
|
|
6
|
+
* indicate the scorer is drifting. Separately reports structural vs
|
|
7
|
+
* LLM means so operators can tell whether the drift is coming from
|
|
8
|
+
* the product (DID review needed) or the LLM layer (exemplar bank
|
|
9
|
+
* recalibration needed).
|
|
10
|
+
*
|
|
11
|
+
* Trigger (per dimension):
|
|
12
|
+
* mean < 0.4 OR stddev > 0.15 — for 3 consecutive windows
|
|
13
|
+
*
|
|
14
|
+
* Hysteresis: caller supplies `lastTriggerAt`; the monitor won't fire
|
|
15
|
+
* again within `recoveryMs` (default 7d) of the previous emission.
|
|
16
|
+
*/
|
|
17
|
+
// ── Constants ───────────────────────────────────────────────────────
|
|
18
|
+
export const DEFAULT_MEAN_THRESHOLD = 0.4;
|
|
19
|
+
export const DEFAULT_STDDEV_THRESHOLD = 0.15;
|
|
20
|
+
/** Number of consecutive windows that must violate to trigger. */
|
|
21
|
+
export const DEFAULT_CONSECUTIVE_WINDOWS = 3;
|
|
22
|
+
export const DEFAULT_WINDOW_DAYS = 30;
|
|
23
|
+
export const DEFAULT_RECOVERY_MS = 7 * 24 * 60 * 60 * 1000;
|
|
24
|
+
// ── Pure helpers ────────────────────────────────────────────────────
|
|
25
|
+
export function mean(values) {
|
|
26
|
+
if (values.length === 0)
|
|
27
|
+
return 0;
|
|
28
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
29
|
+
}
|
|
30
|
+
export function stddev(values) {
|
|
31
|
+
if (values.length === 0)
|
|
32
|
+
return 0;
|
|
33
|
+
const m = mean(values);
|
|
34
|
+
const variance = values.reduce((acc, v) => acc + (v - m) * (v - m), 0) / values.length;
|
|
35
|
+
return Math.sqrt(variance);
|
|
36
|
+
}
|
|
37
|
+
export function computeWindowStats(events, windowStartMs, windowEndMs, meanThreshold, stddevThreshold) {
|
|
38
|
+
const composites = [];
|
|
39
|
+
const structural = [];
|
|
40
|
+
const llm = [];
|
|
41
|
+
let deterministicFlags = 0;
|
|
42
|
+
for (const e of events) {
|
|
43
|
+
if (typeof e.compositeScore === 'number')
|
|
44
|
+
composites.push(e.compositeScore);
|
|
45
|
+
if (e.layer2ResultJson) {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(e.layer2ResultJson);
|
|
48
|
+
if (typeof parsed.score === 'number')
|
|
49
|
+
structural.push(parsed.score);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// ignore malformed entry
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (e.layer3ResultJson) {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(e.layer3ResultJson);
|
|
58
|
+
const candidate = parsed.domainIntent ?? parsed.principleAlignment;
|
|
59
|
+
if (typeof candidate === 'number')
|
|
60
|
+
llm.push(candidate);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// ignore malformed entry
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (e.layer1ResultJson) {
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(e.layer1ResultJson);
|
|
69
|
+
if (parsed.hardGated)
|
|
70
|
+
deterministicFlags++;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// ignore malformed entry
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const m = mean(composites);
|
|
78
|
+
const s = stddev(composites);
|
|
79
|
+
return {
|
|
80
|
+
count: composites.length,
|
|
81
|
+
mean: m,
|
|
82
|
+
stddev: s,
|
|
83
|
+
structuralMean: mean(structural),
|
|
84
|
+
llmMean: mean(llm),
|
|
85
|
+
deterministicFlags,
|
|
86
|
+
windowStartMs,
|
|
87
|
+
windowEndMs,
|
|
88
|
+
violates: composites.length > 0 && (m < meanThreshold || s > stddevThreshold),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function computeTrend(windows) {
|
|
92
|
+
if (windows.length < 2)
|
|
93
|
+
return 'stable';
|
|
94
|
+
// Windows ordered newest-first; trend is the direction of mean over time.
|
|
95
|
+
const newestMean = windows[0].mean;
|
|
96
|
+
const oldestMean = windows[windows.length - 1].mean;
|
|
97
|
+
const delta = newestMean - oldestMean;
|
|
98
|
+
if (Math.abs(delta) < 0.05)
|
|
99
|
+
return 'stable';
|
|
100
|
+
return delta > 0 ? 'increasing' : 'decreasing';
|
|
101
|
+
}
|
|
102
|
+
export function describeDriftSource(windows) {
|
|
103
|
+
const structural = mean(windows.map((w) => w.structuralMean));
|
|
104
|
+
const llm = mean(windows.map((w) => w.llmMean));
|
|
105
|
+
const flags = windows.reduce((acc, w) => acc + w.deterministicFlags, 0);
|
|
106
|
+
let note;
|
|
107
|
+
if (llm < structural - 0.15)
|
|
108
|
+
note = 'LLM-layer drift (consider recalibrating exemplar bank)';
|
|
109
|
+
else if (structural < llm - 0.15)
|
|
110
|
+
note = 'Structural drift (review DID corpus + compile artifacts)';
|
|
111
|
+
else if (flags > 0)
|
|
112
|
+
note = `Mixed drift with ${flags} hard-gated events (review DID scope)`;
|
|
113
|
+
else
|
|
114
|
+
note = 'Uniform drift across layers (product review recommended)';
|
|
115
|
+
return {
|
|
116
|
+
structuralScoreMean: structural,
|
|
117
|
+
llmScoreMean: llm,
|
|
118
|
+
note,
|
|
119
|
+
deterministicFlags: flags,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function detectSoulDrift(dimension, deps) {
|
|
123
|
+
const cfg = deps.config ?? {};
|
|
124
|
+
const meanThreshold = cfg.meanThreshold ?? DEFAULT_MEAN_THRESHOLD;
|
|
125
|
+
const stddevThreshold = cfg.stddevThreshold ?? DEFAULT_STDDEV_THRESHOLD;
|
|
126
|
+
const consecutive = cfg.consecutiveWindows ?? DEFAULT_CONSECUTIVE_WINDOWS;
|
|
127
|
+
const windowDays = cfg.windowDays ?? DEFAULT_WINDOW_DAYS;
|
|
128
|
+
const recoveryMs = cfg.recoveryMs ?? DEFAULT_RECOVERY_MS;
|
|
129
|
+
const nowMs = (deps.now ?? (() => Date.now()))();
|
|
130
|
+
// Hysteresis
|
|
131
|
+
const lastTriggerAt = deps.getLastTriggerAt?.(dimension);
|
|
132
|
+
if (lastTriggerAt) {
|
|
133
|
+
const lastMs = Date.parse(lastTriggerAt);
|
|
134
|
+
if (!Number.isNaN(lastMs) && nowMs - lastMs < recoveryMs)
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
const windowMs = windowDays * 24 * 60 * 60 * 1000;
|
|
138
|
+
const earliestMs = nowMs - consecutive * windowMs;
|
|
139
|
+
// Fetch events for the combined range.
|
|
140
|
+
const since = new Date(earliestMs).toISOString();
|
|
141
|
+
const events = deps.stateStore.getDidScoringEvents({
|
|
142
|
+
saDimension: dimension,
|
|
143
|
+
since,
|
|
144
|
+
limit: 10_000,
|
|
145
|
+
});
|
|
146
|
+
// Partition into consecutive windows (newest → oldest).
|
|
147
|
+
const windows = [];
|
|
148
|
+
for (let i = 0; i < consecutive; i++) {
|
|
149
|
+
const endMs = nowMs - i * windowMs;
|
|
150
|
+
const startMs = endMs - windowMs;
|
|
151
|
+
const inWindow = events.filter((e) => {
|
|
152
|
+
const t = e.createdAt ? Date.parse(e.createdAt) : NaN;
|
|
153
|
+
return !Number.isNaN(t) && t >= startMs && t < endMs;
|
|
154
|
+
});
|
|
155
|
+
windows.push(computeWindowStats(inWindow, startMs, endMs, meanThreshold, stddevThreshold));
|
|
156
|
+
}
|
|
157
|
+
const allViolate = windows.every((w) => w.violates);
|
|
158
|
+
if (!allViolate)
|
|
159
|
+
return undefined;
|
|
160
|
+
// All windows violated — fire the event.
|
|
161
|
+
const compositeMeans = windows.map((w) => w.mean);
|
|
162
|
+
const compositeStddevs = windows.map((w) => w.stddev);
|
|
163
|
+
const rollingMean = mean(compositeMeans);
|
|
164
|
+
const rollingStdDev = mean(compositeStddevs);
|
|
165
|
+
const trend = computeTrend(windows);
|
|
166
|
+
const driftSource = describeDriftSource(windows);
|
|
167
|
+
const binding = deps.getBinding?.(dimension);
|
|
168
|
+
const principals = binding ? collectPrincipals(binding) : [];
|
|
169
|
+
return {
|
|
170
|
+
type: 'SoulDriftDetected',
|
|
171
|
+
dimension,
|
|
172
|
+
rollingMean,
|
|
173
|
+
rollingStdDev,
|
|
174
|
+
sprintsInViolation: consecutive,
|
|
175
|
+
trend,
|
|
176
|
+
driftSource,
|
|
177
|
+
notifiedPrincipals: principals,
|
|
178
|
+
triggeredAt: new Date(nowMs).toISOString(),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function collectPrincipals(binding) {
|
|
182
|
+
const design = binding.spec.stewardship.designAuthority.principals;
|
|
183
|
+
const engineering = binding.spec.stewardship.engineeringAuthority.principals;
|
|
184
|
+
return Array.from(new Set([...design, ...engineering]));
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=drift-monitor.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SA Exemplar Bank loader (RFC-0008 §B.6.4, §B.10.2).
|
|
3
|
+
*
|
|
4
|
+
* Loads labeled SA-scoring exemplars from `.ai-sdlc/sa-exemplars.yaml`
|
|
5
|
+
* for Phase 2a shadow-mode precision tracking and Phase 2b progression
|
|
6
|
+
* gating. A Phase-2b-ready bank needs ≥ 5 exemplars including at least
|
|
7
|
+
* one true-positive AND one false-positive per dimension — without
|
|
8
|
+
* precision evidence on both sides, the structural layer can't be
|
|
9
|
+
* trusted to contribute to ranking.
|
|
10
|
+
*/
|
|
11
|
+
import type { SaDimension } from '../state/types.js';
|
|
12
|
+
export type ExemplarType = 'true-positive' | 'false-positive' | 'true-negative' | 'false-negative';
|
|
13
|
+
export interface ExemplarIssue {
|
|
14
|
+
title: string;
|
|
15
|
+
body: string;
|
|
16
|
+
}
|
|
17
|
+
export interface Layer1Expected {
|
|
18
|
+
hardGated?: boolean;
|
|
19
|
+
coreViolationCount?: number;
|
|
20
|
+
evolvingViolationCount?: number;
|
|
21
|
+
scopeGate?: Record<string, unknown>;
|
|
22
|
+
constraintViolations?: Record<string, unknown>;
|
|
23
|
+
antiPatternHits?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface Layer2Expected {
|
|
26
|
+
domainRelevance?: number;
|
|
27
|
+
overallCoverage?: number;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
export interface Layer3Expected {
|
|
31
|
+
domainIntent?: number;
|
|
32
|
+
principleAlignment?: number;
|
|
33
|
+
reasoning?: string;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
export interface SaExemplar {
|
|
37
|
+
id: string;
|
|
38
|
+
dimension: SaDimension;
|
|
39
|
+
type: ExemplarType;
|
|
40
|
+
issue: ExemplarIssue;
|
|
41
|
+
layer1Expected?: Layer1Expected;
|
|
42
|
+
layer2Expected?: Layer2Expected;
|
|
43
|
+
layer3Expected?: Layer3Expected;
|
|
44
|
+
verdict: string;
|
|
45
|
+
principle?: string;
|
|
46
|
+
notes?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface SaExemplarBank {
|
|
49
|
+
sa1: SaExemplar[];
|
|
50
|
+
sa2: SaExemplar[];
|
|
51
|
+
}
|
|
52
|
+
export declare const EMPTY_BANK: SaExemplarBank;
|
|
53
|
+
/**
|
|
54
|
+
* Load and partition exemplars by dimension. Returns an empty bank
|
|
55
|
+
* when the file is missing — Phase 2a can run without any exemplars;
|
|
56
|
+
* `validatePhase2bExemplars` gates progression.
|
|
57
|
+
*/
|
|
58
|
+
export declare function loadExemplarBank(filePath: string): SaExemplarBank;
|
|
59
|
+
export interface ExemplarGap {
|
|
60
|
+
dimension: SaDimension | 'overall';
|
|
61
|
+
reason: string;
|
|
62
|
+
}
|
|
63
|
+
export interface ExemplarReadinessResult {
|
|
64
|
+
ready: boolean;
|
|
65
|
+
gaps: ExemplarGap[];
|
|
66
|
+
}
|
|
67
|
+
export declare const MIN_TOTAL_EXEMPLARS = 5;
|
|
68
|
+
export declare function validatePhase2bExemplars(bank: SaExemplarBank): ExemplarReadinessResult;
|
|
69
|
+
export interface LayerPrecision {
|
|
70
|
+
truePositives: number;
|
|
71
|
+
falsePositives: number;
|
|
72
|
+
trueNegatives: number;
|
|
73
|
+
falseNegatives: number;
|
|
74
|
+
precision: number;
|
|
75
|
+
recall: number;
|
|
76
|
+
}
|
|
77
|
+
export declare function computeLayerPrecision(exemplars: readonly SaExemplar[]): LayerPrecision;
|
|
78
|
+
//# sourceMappingURL=exemplar-bank.d.ts.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SA Exemplar Bank loader (RFC-0008 §B.6.4, §B.10.2).
|
|
3
|
+
*
|
|
4
|
+
* Loads labeled SA-scoring exemplars from `.ai-sdlc/sa-exemplars.yaml`
|
|
5
|
+
* for Phase 2a shadow-mode precision tracking and Phase 2b progression
|
|
6
|
+
* gating. A Phase-2b-ready bank needs ≥ 5 exemplars including at least
|
|
7
|
+
* one true-positive AND one false-positive per dimension — without
|
|
8
|
+
* precision evidence on both sides, the structural layer can't be
|
|
9
|
+
* trusted to contribute to ranking.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
12
|
+
import { parse as parseYaml } from 'yaml';
|
|
13
|
+
// ── Loader ──────────────────────────────────────────────────────────
|
|
14
|
+
export const EMPTY_BANK = Object.freeze({
|
|
15
|
+
sa1: [],
|
|
16
|
+
sa2: [],
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Load and partition exemplars by dimension. Returns an empty bank
|
|
20
|
+
* when the file is missing — Phase 2a can run without any exemplars;
|
|
21
|
+
* `validatePhase2bExemplars` gates progression.
|
|
22
|
+
*/
|
|
23
|
+
export function loadExemplarBank(filePath) {
|
|
24
|
+
if (!existsSync(filePath))
|
|
25
|
+
return { sa1: [], sa2: [] };
|
|
26
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
27
|
+
let doc;
|
|
28
|
+
try {
|
|
29
|
+
doc = parseYaml(raw);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
throw new Error(`Failed to parse SA exemplar bank at ${filePath}: ${err.message}`);
|
|
33
|
+
}
|
|
34
|
+
if (!doc || typeof doc !== 'object' || !('exemplars' in doc)) {
|
|
35
|
+
throw new Error(`SA exemplar bank ${filePath} must contain a top-level "exemplars" array`);
|
|
36
|
+
}
|
|
37
|
+
const list = doc.exemplars;
|
|
38
|
+
if (!Array.isArray(list)) {
|
|
39
|
+
throw new Error(`SA exemplar bank ${filePath}: "exemplars" must be an array`);
|
|
40
|
+
}
|
|
41
|
+
const sa1 = [];
|
|
42
|
+
const sa2 = [];
|
|
43
|
+
for (const entry of list) {
|
|
44
|
+
const ex = validateExemplar(entry);
|
|
45
|
+
if (ex.dimension === 'SA-1')
|
|
46
|
+
sa1.push(ex);
|
|
47
|
+
else
|
|
48
|
+
sa2.push(ex);
|
|
49
|
+
}
|
|
50
|
+
return { sa1, sa2 };
|
|
51
|
+
}
|
|
52
|
+
function validateExemplar(entry) {
|
|
53
|
+
if (!entry || typeof entry !== 'object') {
|
|
54
|
+
throw new Error(`Exemplar must be an object`);
|
|
55
|
+
}
|
|
56
|
+
const e = entry;
|
|
57
|
+
const id = requireString(e, 'id');
|
|
58
|
+
const dimension = requireEnum(e, 'dimension', ['SA-1', 'SA-2']);
|
|
59
|
+
const type = requireEnum(e, 'type', [
|
|
60
|
+
'true-positive',
|
|
61
|
+
'false-positive',
|
|
62
|
+
'true-negative',
|
|
63
|
+
'false-negative',
|
|
64
|
+
]);
|
|
65
|
+
const issueVal = e.issue;
|
|
66
|
+
if (!issueVal || typeof issueVal !== 'object') {
|
|
67
|
+
throw new Error(`Exemplar "${id}" missing required "issue" object`);
|
|
68
|
+
}
|
|
69
|
+
const issueObj = issueVal;
|
|
70
|
+
const issue = {
|
|
71
|
+
title: requireString(issueObj, 'title'),
|
|
72
|
+
body: requireString(issueObj, 'body'),
|
|
73
|
+
};
|
|
74
|
+
const verdict = requireString(e, 'verdict');
|
|
75
|
+
return {
|
|
76
|
+
id,
|
|
77
|
+
dimension,
|
|
78
|
+
type,
|
|
79
|
+
issue,
|
|
80
|
+
layer1Expected: e.layer1Expected,
|
|
81
|
+
layer2Expected: e.layer2Expected,
|
|
82
|
+
layer3Expected: e.layer3Expected,
|
|
83
|
+
verdict,
|
|
84
|
+
principle: typeof e.principle === 'string' ? e.principle : undefined,
|
|
85
|
+
notes: typeof e.notes === 'string' ? e.notes : undefined,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function requireString(obj, key) {
|
|
89
|
+
const v = obj[key];
|
|
90
|
+
if (typeof v !== 'string' || v.length === 0) {
|
|
91
|
+
throw new Error(`Field "${key}" must be a non-empty string`);
|
|
92
|
+
}
|
|
93
|
+
return v;
|
|
94
|
+
}
|
|
95
|
+
function requireEnum(obj, key, options) {
|
|
96
|
+
const v = obj[key];
|
|
97
|
+
if (typeof v !== 'string' || !options.includes(v)) {
|
|
98
|
+
throw new Error(`Field "${key}" must be one of: ${options.join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
return v;
|
|
101
|
+
}
|
|
102
|
+
export const MIN_TOTAL_EXEMPLARS = 5;
|
|
103
|
+
export function validatePhase2bExemplars(bank) {
|
|
104
|
+
const gaps = [];
|
|
105
|
+
const total = bank.sa1.length + bank.sa2.length;
|
|
106
|
+
if (total < MIN_TOTAL_EXEMPLARS) {
|
|
107
|
+
gaps.push({
|
|
108
|
+
dimension: 'overall',
|
|
109
|
+
reason: `Need ≥${MIN_TOTAL_EXEMPLARS} exemplars total (have ${total})`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
for (const dimension of ['SA-1', 'SA-2']) {
|
|
113
|
+
const slice = dimension === 'SA-1' ? bank.sa1 : bank.sa2;
|
|
114
|
+
if (slice.length === 0) {
|
|
115
|
+
gaps.push({
|
|
116
|
+
dimension,
|
|
117
|
+
reason: `Need ≥1 exemplar for ${dimension}`,
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const hasTP = slice.some((e) => e.type === 'true-positive');
|
|
122
|
+
const hasFP = slice.some((e) => e.type === 'false-positive');
|
|
123
|
+
if (!hasTP) {
|
|
124
|
+
gaps.push({
|
|
125
|
+
dimension,
|
|
126
|
+
reason: `${dimension} needs ≥1 true-positive exemplar`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (!hasFP) {
|
|
130
|
+
gaps.push({
|
|
131
|
+
dimension,
|
|
132
|
+
reason: `${dimension} needs ≥1 false-positive exemplar (regression tracking)`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { ready: gaps.length === 0, gaps };
|
|
137
|
+
}
|
|
138
|
+
export function computeLayerPrecision(exemplars) {
|
|
139
|
+
const tp = exemplars.filter((e) => e.type === 'true-positive').length;
|
|
140
|
+
const fp = exemplars.filter((e) => e.type === 'false-positive').length;
|
|
141
|
+
const tn = exemplars.filter((e) => e.type === 'true-negative').length;
|
|
142
|
+
const fn = exemplars.filter((e) => e.type === 'false-negative').length;
|
|
143
|
+
const precision = tp + fp === 0 ? 0 : tp / (tp + fp);
|
|
144
|
+
const recall = tp + fn === 0 ? 0 : tp / (tp + fn);
|
|
145
|
+
return {
|
|
146
|
+
truePositives: tp,
|
|
147
|
+
falsePositives: fp,
|
|
148
|
+
trueNegatives: tn,
|
|
149
|
+
falseNegatives: fn,
|
|
150
|
+
precision,
|
|
151
|
+
recall,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=exemplar-bank.js.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SA feedback store (RFC-0008 Addendum B §B.8).
|
|
3
|
+
*
|
|
4
|
+
* Wraps the `did_feedback_events` table and exposes precision
|
|
5
|
+
* aggregations used by the feedback flywheel (AISDLC-65 Cκ
|
|
6
|
+
* calibration, AISDLC-66 phase-weight auto-calibration).
|
|
7
|
+
*
|
|
8
|
+
* Signal semantics (§B.8.1):
|
|
9
|
+
* accept — admitted item was correctly scored (true-positive on positive path)
|
|
10
|
+
* dismiss — admitted item should NOT have been (false-positive)
|
|
11
|
+
* escalate — scored too low; should have been ranked higher (false-negative)
|
|
12
|
+
* override — HC_override bypass triggered; feedback auto-emitted
|
|
13
|
+
*/
|
|
14
|
+
import type { StateStore } from '../state/store.js';
|
|
15
|
+
import type { DidFeedbackEventRecord, FeedbackSignal, SaDimension } from '../state/types.js';
|
|
16
|
+
export interface RecordFeedbackInput {
|
|
17
|
+
didName: string;
|
|
18
|
+
issueNumber: number;
|
|
19
|
+
dimension: SaDimension;
|
|
20
|
+
signal: FeedbackSignal;
|
|
21
|
+
principal?: string;
|
|
22
|
+
/** Optional category label — e.g. product/design/engineering pillar, or custom. */
|
|
23
|
+
category?: string;
|
|
24
|
+
/** Layer 2 structural score at admission time (for precision tracking). */
|
|
25
|
+
structuralScore?: number;
|
|
26
|
+
/** Layer 3 LLM score at admission time. */
|
|
27
|
+
llmScore?: number;
|
|
28
|
+
/** Composite SA score. */
|
|
29
|
+
compositeScore?: number;
|
|
30
|
+
notes?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface PrecisionWindow {
|
|
33
|
+
/** ISO timestamp — filter feedback events newer than this. */
|
|
34
|
+
since?: string;
|
|
35
|
+
/** Filter to one dimension. */
|
|
36
|
+
dimension?: SaDimension;
|
|
37
|
+
}
|
|
38
|
+
export interface PrecisionResult {
|
|
39
|
+
/** Sample size after filters. */
|
|
40
|
+
sampleSize: number;
|
|
41
|
+
/** Directionally-correct count (accept/escalate when score was high, dismiss when score was low). */
|
|
42
|
+
correct: number;
|
|
43
|
+
/** Precision = correct / sampleSize. 0 when sampleSize=0. */
|
|
44
|
+
precision: number;
|
|
45
|
+
}
|
|
46
|
+
export interface CategoryFalsePositive {
|
|
47
|
+
category: string;
|
|
48
|
+
sampleSize: number;
|
|
49
|
+
falsePositiveCount: number;
|
|
50
|
+
falsePositiveRate: number;
|
|
51
|
+
}
|
|
52
|
+
export declare class SAFeedbackStore {
|
|
53
|
+
private readonly store;
|
|
54
|
+
constructor(store: StateStore);
|
|
55
|
+
record(input: RecordFeedbackInput): number;
|
|
56
|
+
list(window?: PrecisionWindow): DidFeedbackEventRecord[];
|
|
57
|
+
/**
|
|
58
|
+
* Directional correctness of the Layer 2 structural score.
|
|
59
|
+
*
|
|
60
|
+
* signal=accept + structural ≥ 0.5 → correct (high score → accepted)
|
|
61
|
+
* signal=accept + structural < 0.5 → incorrect (low score → accepted anyway, underconfident)
|
|
62
|
+
* signal=dismiss + structural ≥ 0.5 → incorrect (high score → dismissed, overconfident)
|
|
63
|
+
* signal=dismiss + structural < 0.5 → correct (low score → dismissed)
|
|
64
|
+
* signal=escalate + structural < 0.5 → correct (low score → escalated, was underscored)
|
|
65
|
+
* signal=escalate + structural ≥ 0.5 → incorrect (high score → escalated? unusual)
|
|
66
|
+
* signal=override → EXCLUDED (bypass, not a judgement on structural)
|
|
67
|
+
*/
|
|
68
|
+
structuralPrecision(window?: PrecisionWindow): PrecisionResult;
|
|
69
|
+
llmPrecision(window?: PrecisionWindow): PrecisionResult;
|
|
70
|
+
/**
|
|
71
|
+
* Categories with the highest false-positive rates — i.e. where
|
|
72
|
+
* `dismiss` signals dominate `accept`. Phase 3 calibration
|
|
73
|
+
* (AISDLC-66) should prioritise tuning these.
|
|
74
|
+
*/
|
|
75
|
+
highFalsePositiveCategories(window?: PrecisionWindow, minSampleSize?: number): CategoryFalsePositive[];
|
|
76
|
+
private computeDirectionalPrecision;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Map a GitHub label addition event to a feedback signal, or
|
|
80
|
+
* undefined if the label isn't one of our SA signals.
|
|
81
|
+
*/
|
|
82
|
+
export declare function classifyLabel(label: string): FeedbackSignal | undefined;
|
|
83
|
+
export declare const SA_FEEDBACK_LABELS: readonly string[];
|
|
84
|
+
export interface OverrideFeedbackInput {
|
|
85
|
+
didName: string;
|
|
86
|
+
issueNumber: number;
|
|
87
|
+
/** The override reason from `PriorityScore.override.reason`. */
|
|
88
|
+
reason?: string;
|
|
89
|
+
/** Principal who invoked the override, if known. */
|
|
90
|
+
principal?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Emit an `override` feedback row when an admission score carries an
|
|
94
|
+
* `override` bypass. Safe no-op when `override` is undefined — callers
|
|
95
|
+
* can invoke unconditionally.
|
|
96
|
+
*/
|
|
97
|
+
export declare function recordOverrideFeedback(feedback: SAFeedbackStore, override: {
|
|
98
|
+
reason?: string;
|
|
99
|
+
} | undefined, context: OverrideFeedbackInput): void;
|
|
100
|
+
//# sourceMappingURL=feedback-store.d.ts.map
|