@ai-sdlc/orchestrator 0.9.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 +22 -0
- package/dist/admission-composite.js +85 -2
- package/dist/admission-enrichment.js +12 -2
- package/dist/admission-hc.d.ts +20 -1
- package/dist/admission-hc.js +12 -1
- package/dist/admission-score.d.ts +14 -0
- package/dist/admission-score.js +7 -1
- package/dist/cli/commands/git-remote.d.ts +25 -0
- package/dist/cli/commands/git-remote.js +80 -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 +22 -0
- package/dist/cli/commands/init.js +158 -1
- package/dist/design-authority.d.ts +13 -0
- package/dist/design-authority.js +13 -0
- package/dist/execute.js +8 -5
- package/dist/models/classifier.d.ts +3 -1
- package/dist/models/classifier.js +61 -5
- package/dist/pillar-breakdown.d.ts +23 -1
- package/dist/pillar-breakdown.js +9 -0
- package/dist/runtime/attestations.d.ts +502 -10
- package/dist/runtime/attestations.js +759 -24
- package/dist/runtime/parallelism-flag.d.ts +21 -3
- package/dist/runtime/parallelism-flag.js +27 -6
- package/dist/runtime/port-allocator.d.ts +11 -0
- package/dist/runtime/port-allocator.js +30 -0
- package/package.json +3 -2
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic git env for orchestrator test fixtures (AISDLC-257).
|
|
3
|
+
*
|
|
4
|
+
* Cross-link: mirrors `pipeline-cli/src/__test-helpers/git-env.ts` (AISDLC-253).
|
|
5
|
+
* See that file for the canonical rationale. This copy exists so orchestrator
|
|
6
|
+
* tests don't take a cross-package import dependency on pipeline-cli's test
|
|
7
|
+
* helpers.
|
|
8
|
+
*
|
|
9
|
+
* Why this exists
|
|
10
|
+
* ───────────────
|
|
11
|
+
* Test fixtures that shell out to `git` (e.g. `execSync('git init', { cwd: d })`)
|
|
12
|
+
* are vulnerable to env-var bleed from the parent shell:
|
|
13
|
+
*
|
|
14
|
+
* - `GIT_DIR=...` overrides cwd-based repo discovery — every git command
|
|
15
|
+
* writes into the parent shell's GIT_DIR, NOT the fixture's `cwd`.
|
|
16
|
+
* - `GIT_WORK_TREE=...` ditto for the working tree.
|
|
17
|
+
* - `core.hooksPath` from system/global config can fire husky hooks during
|
|
18
|
+
* fixture commits.
|
|
19
|
+
* - `commit.gpgsign=true` from operator config can break unattended commits.
|
|
20
|
+
*
|
|
21
|
+
* The `cleanGitEnv()` helper in `orchestrator/src/runtime/git-env.ts` strips
|
|
22
|
+
* GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE but still inherits `process.env`
|
|
23
|
+
* in full, meaning `git config user.email` writes still land wherever git
|
|
24
|
+
* resolves config (which may be a polluted GIT_DIR or the operator's global
|
|
25
|
+
* config). AISDLC-257 identified `worktree-pool.integration.test.ts` as the
|
|
26
|
+
* confirmed leak source.
|
|
27
|
+
*
|
|
28
|
+
* `makeGitEnv()` uses an ALLOW-LIST approach rather than a DENY-LIST: it
|
|
29
|
+
* constructs a minimal env from scratch, deliberately omitting GIT_DIR and
|
|
30
|
+
* GIT_WORK_TREE. Identity is provided via `GIT_AUTHOR_*` / `GIT_COMMITTER_*`
|
|
31
|
+
* env vars so fixtures never need `git config user.email`.
|
|
32
|
+
*
|
|
33
|
+
* Pattern (matches pipeline-cli AISDLC-253 + orchestrator AISDLC-241/246):
|
|
34
|
+
*
|
|
35
|
+
* const env = makeGitEnv();
|
|
36
|
+
* execSync('git init -b main', { cwd: repoDir, env, stdio: 'pipe' });
|
|
37
|
+
* // No `git config user.email` needed — identity comes from GIT_AUTHOR_*
|
|
38
|
+
*/
|
|
39
|
+
export declare function makeGitEnv(): NodeJS.ProcessEnv;
|
|
40
|
+
//# sourceMappingURL=git-env.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermetic git env for orchestrator test fixtures (AISDLC-257).
|
|
3
|
+
*
|
|
4
|
+
* Cross-link: mirrors `pipeline-cli/src/__test-helpers/git-env.ts` (AISDLC-253).
|
|
5
|
+
* See that file for the canonical rationale. This copy exists so orchestrator
|
|
6
|
+
* tests don't take a cross-package import dependency on pipeline-cli's test
|
|
7
|
+
* helpers.
|
|
8
|
+
*
|
|
9
|
+
* Why this exists
|
|
10
|
+
* ───────────────
|
|
11
|
+
* Test fixtures that shell out to `git` (e.g. `execSync('git init', { cwd: d })`)
|
|
12
|
+
* are vulnerable to env-var bleed from the parent shell:
|
|
13
|
+
*
|
|
14
|
+
* - `GIT_DIR=...` overrides cwd-based repo discovery — every git command
|
|
15
|
+
* writes into the parent shell's GIT_DIR, NOT the fixture's `cwd`.
|
|
16
|
+
* - `GIT_WORK_TREE=...` ditto for the working tree.
|
|
17
|
+
* - `core.hooksPath` from system/global config can fire husky hooks during
|
|
18
|
+
* fixture commits.
|
|
19
|
+
* - `commit.gpgsign=true` from operator config can break unattended commits.
|
|
20
|
+
*
|
|
21
|
+
* The `cleanGitEnv()` helper in `orchestrator/src/runtime/git-env.ts` strips
|
|
22
|
+
* GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE but still inherits `process.env`
|
|
23
|
+
* in full, meaning `git config user.email` writes still land wherever git
|
|
24
|
+
* resolves config (which may be a polluted GIT_DIR or the operator's global
|
|
25
|
+
* config). AISDLC-257 identified `worktree-pool.integration.test.ts` as the
|
|
26
|
+
* confirmed leak source.
|
|
27
|
+
*
|
|
28
|
+
* `makeGitEnv()` uses an ALLOW-LIST approach rather than a DENY-LIST: it
|
|
29
|
+
* constructs a minimal env from scratch, deliberately omitting GIT_DIR and
|
|
30
|
+
* GIT_WORK_TREE. Identity is provided via `GIT_AUTHOR_*` / `GIT_COMMITTER_*`
|
|
31
|
+
* env vars so fixtures never need `git config user.email`.
|
|
32
|
+
*
|
|
33
|
+
* Pattern (matches pipeline-cli AISDLC-253 + orchestrator AISDLC-241/246):
|
|
34
|
+
*
|
|
35
|
+
* const env = makeGitEnv();
|
|
36
|
+
* execSync('git init -b main', { cwd: repoDir, env, stdio: 'pipe' });
|
|
37
|
+
* // No `git config user.email` needed — identity comes from GIT_AUTHOR_*
|
|
38
|
+
*/
|
|
39
|
+
export function makeGitEnv() {
|
|
40
|
+
return {
|
|
41
|
+
// Minimal OS plumbing.
|
|
42
|
+
PATH: process.env['PATH'] ?? '/usr/bin:/bin',
|
|
43
|
+
HOME: process.env['HOME'] ?? '/tmp',
|
|
44
|
+
...(process.env['TMPDIR'] ? { TMPDIR: process.env['TMPDIR'] } : {}),
|
|
45
|
+
...(process.env['TEMP'] ? { TEMP: process.env['TEMP'] } : {}),
|
|
46
|
+
...(process.env['TMP'] ? { TMP: process.env['TMP'] } : {}),
|
|
47
|
+
// Locale — prevent non-ASCII error strings in git output.
|
|
48
|
+
LANG: process.env['LANG'] ?? 'en_US.UTF-8',
|
|
49
|
+
LC_ALL: 'C',
|
|
50
|
+
// Git identity — supplied via env so `git config user.email/user.name`
|
|
51
|
+
// is never needed inside the fixture (those writes would land in either
|
|
52
|
+
// the fixture's .git/config or, if GIT_DIR is polluted, in the host
|
|
53
|
+
// worktree's config).
|
|
54
|
+
GIT_AUTHOR_NAME: 'Test',
|
|
55
|
+
GIT_AUTHOR_EMAIL: 'test@test.invalid',
|
|
56
|
+
GIT_COMMITTER_NAME: 'Test',
|
|
57
|
+
GIT_COMMITTER_EMAIL: 'test@test.invalid',
|
|
58
|
+
// Disable system + global git config to prevent gpgsign / hookPath /
|
|
59
|
+
// user-config bleed from the operator's machine.
|
|
60
|
+
GIT_CONFIG_NOSYSTEM: '1',
|
|
61
|
+
GIT_CONFIG_GLOBAL: '/dev/null',
|
|
62
|
+
// Disable husky so the calling project's pre-commit hooks don't fire.
|
|
63
|
+
HUSKY: '0',
|
|
64
|
+
// Suppress credential helpers / interactive prompts.
|
|
65
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
66
|
+
GIT_SSH_COMMAND: 'ssh -o BatchMode=yes',
|
|
67
|
+
// Note: deliberately NO `GIT_DIR` / `GIT_WORK_TREE` keys — by omitting
|
|
68
|
+
// them from the returned object, child processes inherit nothing for
|
|
69
|
+
// those vars (the env REPLACES the parent's env when passed to execSync,
|
|
70
|
+
// it doesn't merge). That's the load-bearing guarantee against the leak.
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=git-env.js.map
|
|
@@ -64,4 +64,26 @@ export interface AdmissionCompositeOptions {
|
|
|
64
64
|
priorityInputOverrides?: Partial<PriorityInput>;
|
|
65
65
|
}
|
|
66
66
|
export declare function computeAdmissionComposite(input: AdmissionInput, config?: PriorityConfig, options?: AdmissionCompositeOptions): AdmissionComposite;
|
|
67
|
+
/**
|
|
68
|
+
* Compute admission confidence in [0, 1] as a 50/50 blend of two
|
|
69
|
+
* independent evidence channels:
|
|
70
|
+
*
|
|
71
|
+
* 1. **Mapper coverage** — fraction of `ADMISSION_MAPPER_FIELDS`
|
|
72
|
+
* explicitly populated on the derived `PriorityInput`. Captures
|
|
73
|
+
* "how much issue/backlog signal did `mapIssueToPriorityInput`
|
|
74
|
+
* extract".
|
|
75
|
+
*
|
|
76
|
+
* 2. **Enrichment loaded** — fraction of RFC-0008 enrichment slots
|
|
77
|
+
* present on the input (or supplied via `options`). Captures "how
|
|
78
|
+
* much external context did the enrichment readers contribute".
|
|
79
|
+
*
|
|
80
|
+
* Bug fixed (AISDLC-172 / RFC-0009 §13 OQ-9): previously
|
|
81
|
+
* `computeConfidence(priorityInput)` from `priority.ts` was used, which
|
|
82
|
+
* counts against the full 16-field `SCORABLE_FIELDS` list and ignores
|
|
83
|
+
* enrichment success entirely. The result was a hard ~0.5 ceiling on
|
|
84
|
+
* admit confidence even when DID + DSB + maintainers + soul-tracks all
|
|
85
|
+
* loaded — those four positive observations contributed exactly zero
|
|
86
|
+
* to the formula's denominator and zero to its numerator.
|
|
87
|
+
*/
|
|
88
|
+
export declare function computeAdmissionConfidence(input: AdmissionInput, priorityInput: PriorityInput, options?: AdmissionCompositeOptions): number;
|
|
67
89
|
//# sourceMappingURL=admission-composite.d.ts.map
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
import { mapIssueToPriorityInput } from './admission-score.js';
|
|
28
28
|
import { computeAutonomyFactor, computeDefectRiskFactor, computeReadinessFromDesignSystemContext, } from './admission-enrichment.js';
|
|
29
29
|
import { computeAdmissionHumanCurve } from './admission-hc.js';
|
|
30
|
-
import { computeConfidence } from './priority.js';
|
|
31
30
|
const DEFAULT_SIGNAL = 0.5;
|
|
32
31
|
export function computeAdmissionComposite(input, config, options) {
|
|
33
32
|
const timestamp = new Date().toISOString();
|
|
@@ -114,7 +113,7 @@ export function computeAdmissionComposite(input, config, options) {
|
|
|
114
113
|
// display continuity but do not multiply it into `composite`.
|
|
115
114
|
calibration: clampCalibration(config?.calibrationCoefficient),
|
|
116
115
|
},
|
|
117
|
-
confidence:
|
|
116
|
+
confidence: computeAdmissionConfidence(input, priorityInput, options),
|
|
118
117
|
timestamp,
|
|
119
118
|
};
|
|
120
119
|
return {
|
|
@@ -132,6 +131,90 @@ export function computeAdmissionComposite(input, config, options) {
|
|
|
132
131
|
},
|
|
133
132
|
};
|
|
134
133
|
}
|
|
134
|
+
// ── Admission confidence (AISDLC-172) ──────────────────────────────────
|
|
135
|
+
/**
|
|
136
|
+
* `PriorityInput` fields that the admission mapper (`mapIssueToPriorityInput`)
|
|
137
|
+
* actually populates from issue/backlog signals. The full PPA `SCORABLE_FIELDS`
|
|
138
|
+
* list (used by `computeConfidence` in `priority.ts`) includes runtime-only
|
|
139
|
+
* dimensions like `regulatoryUrgency`, `techInflection`, `marketDivergence`,
|
|
140
|
+
* `meetingDecision`, `budgetUtilization`, and `dependencyClearance` — none of
|
|
141
|
+
* which the admission mapper ever sets. Counting against the full 16-field
|
|
142
|
+
* list capped admit confidence at ~9/16 ≈ 0.56 even for fully-shaped issues
|
|
143
|
+
* (RFC-0009 §13 OQ-9 "0.5 ceiling" bug). The mapper-relevant subset is the
|
|
144
|
+
* correct denominator for the mapper-evidence half of the blend.
|
|
145
|
+
*/
|
|
146
|
+
const ADMISSION_MAPPER_FIELDS = [
|
|
147
|
+
'soulAlignment',
|
|
148
|
+
'demandSignal',
|
|
149
|
+
'teamConsensus',
|
|
150
|
+
'builderConviction',
|
|
151
|
+
'complexity',
|
|
152
|
+
'bugSeverity',
|
|
153
|
+
'explicitPriority',
|
|
154
|
+
'competitiveDrift',
|
|
155
|
+
'customerRequestCount',
|
|
156
|
+
];
|
|
157
|
+
/**
|
|
158
|
+
* RFC-0008 enrichment slots whose presence on `AdmissionInput` indicates
|
|
159
|
+
* an enrichment reader successfully loaded its context. This is the
|
|
160
|
+
* "enrichment-success signal" referenced in the OQ-9 hypothesis:
|
|
161
|
+
*
|
|
162
|
+
* - `designSystemContext` ← DSB loader (catalog/token coverage)
|
|
163
|
+
* - `autonomyContext` ← AutonomyPolicy (DID-driven) loader
|
|
164
|
+
* - `codeAreaQuality` ← code-area metrics loader
|
|
165
|
+
* - `designAuthoritySignal` ← maintainers/principals loader
|
|
166
|
+
* - `soulAlignmentOverride` ← soul-tracks SA-1 loader (M5 path)
|
|
167
|
+
*
|
|
168
|
+
* Total slots is `5`. Each loaded slot contributes `1/5` to the
|
|
169
|
+
* enrichment-evidence half of the confidence blend.
|
|
170
|
+
*/
|
|
171
|
+
const ADMISSION_ENRICHMENT_SLOT_COUNT = 5;
|
|
172
|
+
function countLoadedEnrichmentSlots(input, options) {
|
|
173
|
+
let loaded = 0;
|
|
174
|
+
if (input.designSystemContext)
|
|
175
|
+
loaded++;
|
|
176
|
+
if (input.autonomyContext)
|
|
177
|
+
loaded++;
|
|
178
|
+
if (input.codeAreaQuality)
|
|
179
|
+
loaded++;
|
|
180
|
+
if (input.designAuthoritySignal)
|
|
181
|
+
loaded++;
|
|
182
|
+
if (options?.soulAlignmentOverride !== undefined)
|
|
183
|
+
loaded++;
|
|
184
|
+
return loaded;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Compute admission confidence in [0, 1] as a 50/50 blend of two
|
|
188
|
+
* independent evidence channels:
|
|
189
|
+
*
|
|
190
|
+
* 1. **Mapper coverage** — fraction of `ADMISSION_MAPPER_FIELDS`
|
|
191
|
+
* explicitly populated on the derived `PriorityInput`. Captures
|
|
192
|
+
* "how much issue/backlog signal did `mapIssueToPriorityInput`
|
|
193
|
+
* extract".
|
|
194
|
+
*
|
|
195
|
+
* 2. **Enrichment loaded** — fraction of RFC-0008 enrichment slots
|
|
196
|
+
* present on the input (or supplied via `options`). Captures "how
|
|
197
|
+
* much external context did the enrichment readers contribute".
|
|
198
|
+
*
|
|
199
|
+
* Bug fixed (AISDLC-172 / RFC-0009 §13 OQ-9): previously
|
|
200
|
+
* `computeConfidence(priorityInput)` from `priority.ts` was used, which
|
|
201
|
+
* counts against the full 16-field `SCORABLE_FIELDS` list and ignores
|
|
202
|
+
* enrichment success entirely. The result was a hard ~0.5 ceiling on
|
|
203
|
+
* admit confidence even when DID + DSB + maintainers + soul-tracks all
|
|
204
|
+
* loaded — those four positive observations contributed exactly zero
|
|
205
|
+
* to the formula's denominator and zero to its numerator.
|
|
206
|
+
*/
|
|
207
|
+
export function computeAdmissionConfidence(input, priorityInput, options) {
|
|
208
|
+
let providedMapper = 0;
|
|
209
|
+
for (const field of ADMISSION_MAPPER_FIELDS) {
|
|
210
|
+
if (priorityInput[field] !== undefined)
|
|
211
|
+
providedMapper++;
|
|
212
|
+
}
|
|
213
|
+
const mapperFraction = providedMapper / ADMISSION_MAPPER_FIELDS.length;
|
|
214
|
+
const loadedEnrichment = countLoadedEnrichmentSlots(input, options);
|
|
215
|
+
const enrichmentFraction = loadedEnrichment / ADMISSION_ENRICHMENT_SLOT_COUNT;
|
|
216
|
+
return 0.5 * mapperFraction + 0.5 * enrichmentFraction;
|
|
217
|
+
}
|
|
135
218
|
function clamp01(value) {
|
|
136
219
|
return Math.min(1, Math.max(0, value));
|
|
137
220
|
}
|
|
@@ -225,16 +225,26 @@ export function computeDesignAuthorityWeight(signal) {
|
|
|
225
225
|
function buildDesignAuthoritySignal(input, ctx) {
|
|
226
226
|
if (!ctx.designSystemBinding)
|
|
227
227
|
return undefined;
|
|
228
|
+
// AISDLC-171: surface whether the DSB declares any design authority
|
|
229
|
+
// principals at all. This is a diagnostic flag — it does NOT participate
|
|
230
|
+
// in the HC_design weight calculation (per RFC-0008 §14.2, only
|
|
231
|
+
// principals who participate as author/commenter can emit HC_design).
|
|
232
|
+
// Surfacing it lets pillarBreakdown distinguish "DSB has no design
|
|
233
|
+
// authority structure" from "DSB has design authority but no principal
|
|
234
|
+
// participated in this issue".
|
|
235
|
+
const principalsDeclared = (ctx.designSystemBinding.spec.stewardship.designAuthority.principals?.length ?? 0) > 0;
|
|
228
236
|
const { isDesignAuthority, signalType } = checkDesignAuthority({
|
|
229
237
|
authorLogin: input.authorLogin,
|
|
230
238
|
commenterLogins: input.commenterLogins,
|
|
231
239
|
labels: input.labels,
|
|
232
240
|
}, ctx.designSystemBinding);
|
|
233
|
-
if (!isDesignAuthority)
|
|
234
|
-
return { isDesignAuthority: false };
|
|
241
|
+
if (!isDesignAuthority) {
|
|
242
|
+
return { isDesignAuthority: false, principalsDeclared };
|
|
243
|
+
}
|
|
235
244
|
return {
|
|
236
245
|
isDesignAuthority: true,
|
|
237
246
|
signalType,
|
|
247
|
+
principalsDeclared,
|
|
238
248
|
...(ctx.areaComplianceScore !== undefined
|
|
239
249
|
? { areaComplianceScore: ctx.areaComplianceScore }
|
|
240
250
|
: {}),
|
package/dist/admission-hc.d.ts
CHANGED
|
@@ -34,8 +34,27 @@ export interface AdmissionHumanCurveResult {
|
|
|
34
34
|
hcConsensus: number;
|
|
35
35
|
/** Signed meeting-decision signal in [-1, 1] (0 = neutral when absent). */
|
|
36
36
|
hcDecision: number;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Signed design-authority signal in [-1, 1].
|
|
39
|
+
*
|
|
40
|
+
* AISDLC-171: this channel ONLY fires when one of the issue's
|
|
41
|
+
* participants (author or commenters) is a principal listed in
|
|
42
|
+
* `DesignSystemBinding.spec.stewardship.designAuthority.principals`.
|
|
43
|
+
* Per RFC-0008 §14.2, non-principal participants' design opinions
|
|
44
|
+
* route through `hcConsensus` instead — `hcDesign` carries
|
|
45
|
+
* specifically the design-authority leadership signal. To distinguish
|
|
46
|
+
* "no DSB at all" from "DSB exists but no design principal
|
|
47
|
+
* participated", consult `designAuthorityConfigured` on this result
|
|
48
|
+
* (and on `pillarBreakdown.shared.hcComposite`).
|
|
49
|
+
*/
|
|
38
50
|
hcDesign: number;
|
|
51
|
+
/**
|
|
52
|
+
* Diagnostic flag (AISDLC-171) — true when the resolved DSB declared
|
|
53
|
+
* `stewardship.designAuthority.principals` entries. Always undefined
|
|
54
|
+
* when no DSB was supplied (preDesignSystem). Surfaces the
|
|
55
|
+
* "configured but inactive" state without altering hcDesign weight.
|
|
56
|
+
*/
|
|
57
|
+
designAuthorityConfigured?: boolean;
|
|
39
58
|
/** Weighted sum before tanh. */
|
|
40
59
|
hcRaw: number;
|
|
41
60
|
/** `tanh(hcRaw)` — the HC value consumed by the admission composite. */
|
package/dist/admission-hc.js
CHANGED
|
@@ -70,12 +70,23 @@ export function computeAdmissionHumanCurve(input) {
|
|
|
70
70
|
const hcConsensus = deriveHcConsensus(input);
|
|
71
71
|
const hcDecision = deriveHcDecision(input);
|
|
72
72
|
const hcDesign = deriveHcDesign(input.designAuthoritySignal);
|
|
73
|
+
// AISDLC-171: surface the diagnostic flag from the enriched signal so
|
|
74
|
+
// pillarBreakdown can render the "configured but inactive" state.
|
|
75
|
+
const designAuthorityConfigured = input.designAuthoritySignal?.principalsDeclared;
|
|
73
76
|
const hcRaw = HC_WEIGHTS.explicit * hcExplicit +
|
|
74
77
|
HC_WEIGHTS.consensus * hcConsensus +
|
|
75
78
|
HC_WEIGHTS.decision * hcDecision +
|
|
76
79
|
HC_WEIGHTS.design * hcDesign;
|
|
77
80
|
const hcComposite = Math.tanh(hcRaw);
|
|
78
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
hcExplicit,
|
|
83
|
+
hcConsensus,
|
|
84
|
+
hcDecision,
|
|
85
|
+
hcDesign,
|
|
86
|
+
hcRaw,
|
|
87
|
+
hcComposite,
|
|
88
|
+
...(designAuthorityConfigured !== undefined ? { designAuthorityConfigured } : {}),
|
|
89
|
+
};
|
|
79
90
|
}
|
|
80
91
|
function clamp(value, min, max) {
|
|
81
92
|
return Math.min(max, Math.max(min, value));
|
|
@@ -102,6 +102,20 @@ export interface DesignAuthoritySignal {
|
|
|
102
102
|
signalType?: DesignAuthoritySignalType;
|
|
103
103
|
/** The issue's code area compliance score in [0, 1] (used to modulate weight). */
|
|
104
104
|
areaComplianceScore?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Diagnostic flag (AISDLC-171) — true when the resolved DSB declared
|
|
107
|
+
* one or more `stewardship.designAuthority.principals` entries,
|
|
108
|
+
* regardless of whether any of them participated in this issue.
|
|
109
|
+
*
|
|
110
|
+
* This is intentionally NOT consumed by `computeDesignAuthorityWeight`
|
|
111
|
+
* — RFC-0008 §14.2 requires principal participation for HC_design to
|
|
112
|
+
* fire at any weight. The flag exists so `pillarBreakdown.shared.
|
|
113
|
+
* hcComposite.designAuthorityConfigured` can surface "design authority
|
|
114
|
+
* is configured but absent from this issue" — distinct from "no
|
|
115
|
+
* design authority structure exists at all" — without violating the
|
|
116
|
+
* RFC's §14.2 weighting constraint.
|
|
117
|
+
*/
|
|
118
|
+
principalsDeclared?: boolean;
|
|
105
119
|
}
|
|
106
120
|
export interface AdmissionThresholds {
|
|
107
121
|
minimumScore: number;
|
package/dist/admission-score.js
CHANGED
|
@@ -24,7 +24,13 @@ export function mapIssueToPriorityInput(input) {
|
|
|
24
24
|
const backlog = input.backlogContext;
|
|
25
25
|
// ── Backlog status veto ─────────────────────────────────────
|
|
26
26
|
// Drafts aren't ready to admit; orchestrator should ignore them.
|
|
27
|
-
|
|
27
|
+
// RFC-0011 Phase 4 (AISDLC-115.5): the same veto applies to
|
|
28
|
+
// `Needs Clarification` — running PPA on an unready issue burns
|
|
29
|
+
// scoring effort that gets invalidated on clarification (RFC §2.3 +
|
|
30
|
+
// §7.2). The PPA admission step short-circuits with soulAlignment=0
|
|
31
|
+
// so the composite scorer treats the issue as not-admissible without
|
|
32
|
+
// calling out to the design / autonomy / trust enrichment layers.
|
|
33
|
+
if (backlog?.status === 'Draft' || backlog?.status === 'Needs Clarification') {
|
|
28
34
|
return {
|
|
29
35
|
itemId: `#${input.issueNumber}`,
|
|
30
36
|
title: input.title,
|
|
@@ -39,6 +39,31 @@ export interface DetectRemoteOptions {
|
|
|
39
39
|
* Detect the GitHub-style org/repo from the project's git origin remote.
|
|
40
40
|
* Returns FALLBACK with detected=false when no remote is configured or
|
|
41
41
|
* when the URL cannot be parsed.
|
|
42
|
+
*
|
|
43
|
+
* The git invocation is hardened against two failure modes (AISDLC-104):
|
|
44
|
+
*
|
|
45
|
+
* 1. **cwd inheritance race under parallel test workers.** Every git
|
|
46
|
+
* command uses `git -C <cwd>` so the working dir is pinned at the
|
|
47
|
+
* git argv level rather than relying solely on `child_process`
|
|
48
|
+
* honouring the `cwd:` spawn option. Both should agree, but `git -C`
|
|
49
|
+
* is a git-internal contract independent of any subprocess cwd
|
|
50
|
+
* inheritance race that can happen when `process.chdir()` is
|
|
51
|
+
* interleaved with subprocess spawn under thread/fork pools.
|
|
52
|
+
*
|
|
53
|
+
* 2. **Host-repo origin bleed via parent-directory walk-up.** If `cwd`
|
|
54
|
+
* contains an invalid `.git` (e.g. an empty directory left by an
|
|
55
|
+
* init test setup) git normally walks UP looking for a real `.git`
|
|
56
|
+
* and can resolve to an ancestor repository — i.e. when run from
|
|
57
|
+
* inside the ai-sdlc-framework checkout the test would silently see
|
|
58
|
+
* the framework's own origin rather than the fallback. We defend by
|
|
59
|
+
* calling `git rev-parse --show-toplevel` first and confirming the
|
|
60
|
+
* reported toplevel matches `cwd` (after symlink resolution). When
|
|
61
|
+
* it doesn't, we treat the directory as not-a-repo and return the
|
|
62
|
+
* fallback rather than reporting the ancestor's remote. This was
|
|
63
|
+
* preferred over `GIT_CEILING_DIRECTORIES` because the ceiling-list
|
|
64
|
+
* semantics only block walking INTO the listed dirs, not up FROM
|
|
65
|
+
* them — empirically `GIT_CEILING_DIRECTORIES=<cwd>` did not stop
|
|
66
|
+
* git from finding a parent repo.
|
|
42
67
|
*/
|
|
43
68
|
export declare function detectGitRemote(opts?: DetectRemoteOptions): RemoteInfo;
|
|
44
69
|
/**
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* remote is configured (e.g. a brand-new local-only repo).
|
|
10
10
|
*/
|
|
11
11
|
import { execSync } from 'node:child_process';
|
|
12
|
+
import { realpathSync } from 'node:fs';
|
|
12
13
|
const FALLBACK = { org: 'your-org', repo: 'your-repo', detected: false };
|
|
13
14
|
/**
|
|
14
15
|
* Parse a single remote URL into org/repo. Supports:
|
|
@@ -50,13 +51,65 @@ export function parseRemoteUrl(url) {
|
|
|
50
51
|
* Detect the GitHub-style org/repo from the project's git origin remote.
|
|
51
52
|
* Returns FALLBACK with detected=false when no remote is configured or
|
|
52
53
|
* when the URL cannot be parsed.
|
|
54
|
+
*
|
|
55
|
+
* The git invocation is hardened against two failure modes (AISDLC-104):
|
|
56
|
+
*
|
|
57
|
+
* 1. **cwd inheritance race under parallel test workers.** Every git
|
|
58
|
+
* command uses `git -C <cwd>` so the working dir is pinned at the
|
|
59
|
+
* git argv level rather than relying solely on `child_process`
|
|
60
|
+
* honouring the `cwd:` spawn option. Both should agree, but `git -C`
|
|
61
|
+
* is a git-internal contract independent of any subprocess cwd
|
|
62
|
+
* inheritance race that can happen when `process.chdir()` is
|
|
63
|
+
* interleaved with subprocess spawn under thread/fork pools.
|
|
64
|
+
*
|
|
65
|
+
* 2. **Host-repo origin bleed via parent-directory walk-up.** If `cwd`
|
|
66
|
+
* contains an invalid `.git` (e.g. an empty directory left by an
|
|
67
|
+
* init test setup) git normally walks UP looking for a real `.git`
|
|
68
|
+
* and can resolve to an ancestor repository — i.e. when run from
|
|
69
|
+
* inside the ai-sdlc-framework checkout the test would silently see
|
|
70
|
+
* the framework's own origin rather than the fallback. We defend by
|
|
71
|
+
* calling `git rev-parse --show-toplevel` first and confirming the
|
|
72
|
+
* reported toplevel matches `cwd` (after symlink resolution). When
|
|
73
|
+
* it doesn't, we treat the directory as not-a-repo and return the
|
|
74
|
+
* fallback rather than reporting the ancestor's remote. This was
|
|
75
|
+
* preferred over `GIT_CEILING_DIRECTORIES` because the ceiling-list
|
|
76
|
+
* semantics only block walking INTO the listed dirs, not up FROM
|
|
77
|
+
* them — empirically `GIT_CEILING_DIRECTORIES=<cwd>` did not stop
|
|
78
|
+
* git from finding a parent repo.
|
|
53
79
|
*/
|
|
54
80
|
export function detectGitRemote(opts = {}) {
|
|
55
81
|
const cwd = opts.cwd ?? process.cwd();
|
|
56
82
|
const exec = opts.execImpl ?? defaultExec;
|
|
83
|
+
// Step 1: confirm cwd is a real git repo whose toplevel IS cwd.
|
|
84
|
+
// If `git rev-parse --show-toplevel` errors OR returns an ancestor,
|
|
85
|
+
// treat as not-a-repo and return FALLBACK. This is the host-repo
|
|
86
|
+
// bleed defense: an empty/invalid `.git/` in cwd causes git to walk
|
|
87
|
+
// UP to a parent repo, and `--show-toplevel` then reports the parent
|
|
88
|
+
// — comparing realpaths catches it.
|
|
89
|
+
let toplevel;
|
|
90
|
+
try {
|
|
91
|
+
toplevel = exec(`git -C ${shellQuote(cwd)} rev-parse --show-toplevel`, {
|
|
92
|
+
cwd,
|
|
93
|
+
encoding: 'utf-8',
|
|
94
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
95
|
+
}).trim();
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return FALLBACK;
|
|
99
|
+
}
|
|
100
|
+
if (!sameDir(toplevel, cwd)) {
|
|
101
|
+
// git resolved to an ancestor repository — host-repo bleed. The
|
|
102
|
+
// operator is in a directory that isn't itself a real git root, so
|
|
103
|
+
// we deliberately do NOT report the ancestor's origin; emit
|
|
104
|
+
// FALLBACK so init prints the explicit "no git origin remote
|
|
105
|
+
// detected" message and substitutes `your-org`.
|
|
106
|
+
return FALLBACK;
|
|
107
|
+
}
|
|
108
|
+
// Step 2: ask for the origin URL. If unset (no remote configured)
|
|
109
|
+
// or unparseable, fall back.
|
|
57
110
|
let url;
|
|
58
111
|
try {
|
|
59
|
-
url = exec(
|
|
112
|
+
url = exec(`git -C ${shellQuote(cwd)} remote get-url origin`, {
|
|
60
113
|
cwd,
|
|
61
114
|
encoding: 'utf-8',
|
|
62
115
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -68,6 +121,32 @@ export function detectGitRemote(opts = {}) {
|
|
|
68
121
|
const parsed = parseRemoteUrl(url);
|
|
69
122
|
return parsed ?? FALLBACK;
|
|
70
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Compare two filesystem paths after symlink + canonicalization to
|
|
126
|
+
* decide whether they refer to the same directory. macOS aliases /tmp
|
|
127
|
+
* to /private/tmp, so a string compare of `cwd` against the toplevel
|
|
128
|
+
* git reports would otherwise fail spuriously. Falls back to literal
|
|
129
|
+
* compare when realpath isn't available (deleted dir, permission).
|
|
130
|
+
*/
|
|
131
|
+
function sameDir(a, b) {
|
|
132
|
+
const norm = (p) => {
|
|
133
|
+
try {
|
|
134
|
+
return realpathSync(p);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return p;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
return norm(a) === norm(b);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Quote a path for safe single-token interpolation into a shell command.
|
|
144
|
+
* Wraps in single quotes and escapes any embedded single quotes by
|
|
145
|
+
* closing the quote, emitting an escaped quote, then reopening.
|
|
146
|
+
*/
|
|
147
|
+
function shellQuote(s) {
|
|
148
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
149
|
+
}
|
|
71
150
|
function defaultExec(cmd, opts) {
|
|
72
151
|
return execSync(cmd, {
|
|
73
152
|
cwd: opts.cwd,
|