@mstar-harness/engine 3.6.2 → 3.7.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.
@@ -57,8 +57,7 @@ export type PrTallyResult = {
57
57
  */
58
58
  export declare function computePrTally(input: PrTallyInput): PrTallyResult;
59
59
  /**
60
- * One accepted PR-review finding in the `mstar.review/v1` envelope (SP3 §
61
- * Schema). `mergeClass` is harness vocab (MERGE_CLASSES); `title`/`body`
60
+ * One accepted PR-review finding in the `mstar.review/v1` envelope (the review envelope schema). `mergeClass` is harness vocab (MERGE_CLASSES); `title`/`body`
62
61
  * are non-empty strings; the rest are optional.
63
62
  */
64
63
  export type MstarReviewFinding = {
@@ -72,7 +71,7 @@ export type MstarReviewFinding = {
72
71
  fingerprint_hint?: string;
73
72
  };
74
73
  /**
75
- * The `mstar.review/v1` envelope (SP3 § Schema) — a parseable review
74
+ * The `mstar.review/v1` envelope (the review envelope schema) — a parseable review
76
75
  * document with harness vocab, sibling to the Markdown pr-review report.
77
76
  * `verdict` is PR_VERDICTS; `tally` (when present) must be a full
78
77
  * `PrTallyResult` (shape-checked) whose `verdict` must equal the top-level
@@ -92,7 +91,7 @@ export type MstarReviewV1 = {
92
91
  };
93
92
  };
94
93
  /**
95
- * Validate a `mstar.review/v1` envelope (SP3 § Schema). Fail-loud sibling
94
+ * Validate a `mstar.review/v1` envelope (the review envelope schema). Fail-loud sibling
96
95
  * of {@link validatePrReviewReport} (the Markdown report validator) —
97
96
  * shares PR_VERDICTS / MERGE_CLASSES only, never reuses the Markdown
98
97
  * parser. Inspector M1 vocab (`comment|request_changes|approve`,
@@ -108,7 +107,7 @@ export type MstarReviewV1 = {
108
107
  export declare function validateMstarReviewV1(doc: unknown): GateResult;
109
108
  /**
110
109
  * Fold already-vetted findings into a complete `mstar.review/v1` envelope
111
- * (SP3 § synthesizeReview). Pure and synchronous — verdict/tally come ONLY
110
+ * . Pure and synchronous — verdict/tally come ONLY
112
111
  * from {@link computePrTally}; no I/O, no GitHub, no store, no seat
113
112
  * dispatch. `findings` pass through untouched; `target` is carried when
114
113
  * provided. When `summary_md` is omitted, {@link defaultReviewSummary}
@@ -126,11 +125,11 @@ export declare function synthesizeReview(input: {
126
125
  *
127
126
  * - `pr` — reviewed PR number `n`.
128
127
  * - `branch` — bare branch, `slug` is the pre-slugged `<branch-slug>`
129
- * (caller slugs; the resolver only guards path safety).
128
+ * (caller slugs; the resolver only guards path safety).
130
129
  * - `diff` — arbitrary changeset. When `headSha` is a non-empty string its
131
- * short form (first 7 hex chars) lands in the filename; when absent or an
132
- * empty string the bare `-diff` stem is used — a missing SHA is **never
133
- * fabricated** (pr-review.md § Local report archive, Filename bullet).
130
+ * short form (first 7 hex chars) lands in the filename; when absent or an
131
+ * empty string the bare `-diff` stem is used — a missing SHA is **never
132
+ * fabricated** (pr-review.md § Local report archive, Filename bullet).
134
133
  */
135
134
  export type PrReportTarget = {
136
135
  kind: "pr";
@@ -148,16 +147,16 @@ export type PrReportTarget = {
148
147
  * never writes and never overwrites):
149
148
  *
150
149
  * - `<YYYY-MM-DD>-pr<N>.md` for PR targets; bare branch →
151
- * `<YYYY-MM-DD>-<branch-slug>.md`; diff with head SHA →
152
- * `<YYYY-MM-DD>-diff-<short-head-sha>.md`; diff without →
153
- * `<YYYY-MM-DD>-diff.md` (never fabricate a SHA).
150
+ * `<YYYY-MM-DD>-<branch-slug>.md`; diff with head SHA →
151
+ * `<YYYY-MM-DD>-diff-<short-head-sha>.md`; diff without →
152
+ * `<YYYY-MM-DD>-diff.md` (never fabricate a SHA).
154
153
  * - `stage: 1 | 2` requires `slug` (the seat Assignment's `<domain>-<seat>`
155
- * slug) and produces the Stage 1/2 evidence-file stem
156
- * `<stem>-stage<1|2>-<slug>.md`.
154
+ * slug) and produces the Stage 1/2 evidence-file stem
155
+ * `<stem>-stage<1|2>-<slug>.md`.
157
156
  * - Same day, same target (same final stem): scans ALL existing files in
158
- * `reportsDir` with that stem and appends `-r2`, `-r3`, ... on collision —
159
- * a prior report is never overwritten. Report files and evidence files
160
- * escalate independently (different stems).
157
+ * `reportsDir` with that stem and appends `-r2`, `-r3`, ... on collision —
158
+ * a prior report is never overwritten. Report files and evidence files
159
+ * escalate independently (different stems).
161
160
  * - `date` defaults to the local calendar date and must be `YYYY-MM-DD`.
162
161
  */
163
162
  export declare function prReviewReportPath(opts: {
@@ -174,22 +173,22 @@ export declare function prReviewReportPath(opts: {
174
173
  *
175
174
  * - `type: pr-review` required.
176
175
  * - `verdict` exactly one of the three verdict tokens (§ Verdict synthesis)
177
- * and CONSISTENT with the tally (any must_fix -> blocked; else any
178
- * should_fix -> needs fixes; else ship it).
176
+ * and CONSISTENT with the tally (any must_fix -> blocked; else any
177
+ * should_fix -> needs fixes; else ship it).
179
178
  * - `score_pct` integer 0-100 and equal to the locked-formula recompute
180
- * from the document's own tally via {@link computePrTally}
181
- * (mismatch = hand-arithmetic drift — the exact defect class this gate
182
- * exists to catch).
179
+ * from the document's own tally via {@link computePrTally}
180
+ * (mismatch = hand-arithmetic drift — the exact defect class this gate
181
+ * exists to catch).
183
182
  * - `tally` flow map with the four classes.
184
183
  * - `comments` tri-state: `posted` (alias `yes`) | `n/a-no-pr` | `failed`.
185
- * The states are distinct: a FAILED POST IS `FAILED`, never
186
- * `n/a-no-pr`; `review_url` must pair accordingly (`http(s)://` for
187
- * posted, `n/a` for n/a-no-pr, a `failed: <gh error summary>` for failed).
184
+ * The states are distinct: a FAILED POST IS `FAILED`, never
185
+ * `n/a-no-pr`; `review_url` must pair accordingly (`http(s)://` for
186
+ * posted, `n/a` for n/a-no-pr, a `failed: <gh error summary>` for failed).
188
187
  * - `generated_at` must be `YYYY-MM-DD` (DATE_RE).
189
188
  * - `tier` optional: `quick | default | deep`; absent is valid (legacy
190
- * reports without tier still pass — SP-A amendment).
189
+ * reports without tier still pass — SP-A amendment).
191
190
  * - `elapsed` optional: non-negative integer minutes; absent is valid
192
- * (legacy reports without elapsed still pass).
191
+ * (legacy reports without elapsed still pass).
193
192
  */
194
193
  export declare function validatePrReviewReport(text: string): GateResult;
195
194
  /** One inline review comment: `path` + `line` in the three-dot diff, RIGHT
@@ -221,14 +220,14 @@ export type ReviewPostPlan = {
221
220
  * 422 fallback stay with the CLI, which owns the network):
222
221
  *
223
222
  * - Resolves the target from `gh pr view --json url,headRefOid` output:
224
- * parse `owner/repo` from `url` ONLY — the BASE repo that owns the PR
225
- * number. `headRepository` (fork head-repo data) is IGNORED: fork PRs
226
- * are legal, and a fork's owner/name must never leak into the API path.
227
- * It is never used as a fallback either.
223
+ * parse `owner/repo` from `url` ONLY — the BASE repo that owns the PR
224
+ * number. `headRepository` (fork head-repo data) is IGNORED: fork PRs
225
+ * are legal, and a fork's owner/name must never leak into the API path.
226
+ * It is never used as a fallback either.
228
227
  * - Missing/invalid `headRefOid` throws — there is no commit_id without
229
- * it.
228
+ * it.
230
229
  * - `event` is always the literal `"COMMENT"`; no other value exists in
231
- * this contract.
230
+ * this contract.
232
231
  * - Inline comments validated per-entry (path / positive line / RIGHT).
233
232
  */
234
233
  export declare function planReviewPost(prView: {
@@ -256,13 +255,13 @@ export type ReviewChangesetMode = "pr" | "branch" | "diff" | "working-tree" | "c
256
255
  * (pr-review.md § Worktree isolation Pre-flight bullet, all modes):
257
256
  *
258
257
  * - Named refs must resolve in modes that HAVE refs (pr / branch / commit);
259
- * establish them with explicit refspecs first.
258
+ * establish them with explicit refspecs first.
260
259
  * - The changeset must be NON-empty in ALL modes — an empty changeset
261
- * reports "no changes to review" and stops before any lens fan-out.
260
+ * reports "no changes to review" and stops before any lens fan-out.
262
261
  * - For working-tree input, untracked-only changes ARE a non-empty
263
- * changeset: the caller folds `git ls-files --others
264
- * --exclude-standard` output into `changesetEmpty: false` when anything
265
- * is listed.
262
+ * changeset: the caller folds `git ls-files --others
263
+ * --exclude-standard` output into `changesetEmpty: false` when anything
264
+ * is listed.
266
265
  */
267
266
  export declare function preflightChangeset(mode: ReviewChangesetMode, probe: {
268
267
  refsResolve: boolean;
@@ -277,12 +276,12 @@ export type PrReviewSizing = {
277
276
  /** True for too-large (>~1000) — advise a split, never auto-blocked. */
278
277
  adviseSplit: boolean;
279
278
  /** Stage 1 collect seats (§ Scale-driven fan-out table). Kept-wave
280
- * qualifier: these seats apply only when the deep collect wave is KEPT —
281
- * by default (pinned diff pack present) the fold dispatches none
282
- * (pr-review.md § Review pipeline fold default). */
279
+ * qualifier: these seats apply only when the deep collect wave is KEPT —
280
+ * by default (pinned diff pack present) the fold dispatches none
281
+ * (pr-review.md § Review pipeline fold default). */
283
282
  collectSeats: 2 | 3;
284
283
  /** File-size watch fired → advise extract/decompose ("decompose, then
285
- * add"). Independent of the diff size. */
284
+ * add"). Independent of the diff size. */
286
285
  fileDecomposeAdvice: boolean;
287
286
  };
288
287
  /**
@@ -290,13 +289,13 @@ export type PrReviewSizing = {
290
289
  * (pr-review.md § Sizing & change shape + § Scale-driven fan-out):
291
290
  *
292
291
  * - ≤~300 reviewable/acceptable → band `small`; >~300 → `large`;
293
- * >~1000 → `too-large` + split advice (a should-fix finding with split
294
- * advice or a verdict note — never auto-`blocked`).
292
+ * >~1000 → `too-large` + split advice (a should-fix finding with split
293
+ * advice or a verdict note — never auto-`blocked`).
295
294
  * - Stage 1 collect seats: small → 2 (code + security); large/too-large →
296
- * 3 by domain.
295
+ * 3 by domain.
297
296
  * - `largestTouchedFileTotal` drives `fileDecomposeAdvice` INDEPENDENTLY
298
- * of the diff size — a small diff materially growing a file past ~1000
299
- * total lines gets "decompose, then add".
297
+ * of the diff size — a small diff materially growing a file past ~1000
298
+ * total lines gets "decompose, then add".
300
299
  */
301
300
  export declare function prReviewSizing(input: {
302
301
  changedLines: number;
@@ -310,23 +309,23 @@ export declare function prReviewSizing(input: {
310
309
  * includes everything. */
311
310
  export type PrReviewTier = "quick" | "default" | "deep";
312
311
  /**
313
- * Per-tier time budget for the `amazing-pr-review` pipeline (SP1 tier
312
+ * Per-tier time budget for the `amazing-pr-review` pipeline (tier
314
313
  * time-budget). Prose SSOT: pr-review.md § Review depth — its Budget column
315
314
  * carries the wall-clock minutes; the per-seat caps below are the engine
316
315
  * contract, rendered into seat prompts, never duplicated as numbers in prose.
317
316
  *
318
317
  * - `wallClockMinutes`: prompt-discipline target for the whole review,
319
- * measured worktree-setup → local report saved by the main agent.
320
- * Overruns are declared in the report `- notes:` — budgets are never a
321
- * host-level hard kill.
318
+ * measured worktree-setup → local report saved by the main agent.
319
+ * Overruns are declared in the report `- notes:` — budgets are never a
320
+ * host-level hard kill.
322
321
  * - `maxSeats`: review seats only — Stage 2 domain seats + the independent
323
- * cross-domain security seat; NOT Stage 1 collect seats (collect fan-out
324
- * stays governed by pr-review.md § Scale-driven fan-out).
322
+ * cross-domain security seat; NOT Stage 1 collect seats (collect fan-out
323
+ * stays governed by pr-review.md § Scale-driven fan-out).
325
324
  * - `perSeatFindingsCap` / `evidenceTokensCap` / `fileOpenCap`: per-seat
326
- * expansion stops (findings / evidence payload tokens / file opens; the
327
- * pinned diff snapshot read does not count). Baseline assumption: 100
328
- * tok/s output — wall-clock is dominated by reads/tool latency, which
329
- * `fileOpenCap` bounds.
325
+ * expansion stops (findings / evidence payload tokens / file opens; the
326
+ * pinned diff snapshot read does not count). Baseline assumption: 100
327
+ * tok/s output — wall-clock is dominated by reads/tool latency, which
328
+ * `fileOpenCap` bounds.
330
329
  *
331
330
  * No new sizing bands: the table references tiers only (pr-review.md
332
331
  * § Sizing & change shape stays the only sizing SSOT). */
@@ -349,18 +348,18 @@ export type PrReviewSeatPromptOptions = {
349
348
  securitySeat?: boolean;
350
349
  tier?: PrReviewTier;
351
350
  /** Absolute path to the pinned diff snapshot written by `worktree-setup`
352
- * (review artifact beside the sidecar). Non-empty → the prompt gains a
353
- * read-first ingredient line pointing at it. */
351
+ * (review artifact beside the sidecar). Non-empty → the prompt gains a
352
+ * read-first ingredient line pointing at it. */
354
353
  diffFile?: string;
355
354
  /** Stage-2 only (`true` + `stage: 1` throws): the collect wave was folded
356
- * onto this domain seat — the deep-tier fold default (pinned diff pack
357
- * present; SSOT pr-review.md § Review pipeline; prose "collection folded
358
- * in = seat reuse" = § Review depth default-tier row). Kept-wave
359
- * exceptions dispatch collect seats instead. Requires a non-empty
360
- * `diffFile`, and the independent cross-domain security seat is never
361
- * folded — either contradiction throws. The prompt gains one bullet after
362
- * the `## Budget` block telling the seat to do its own collection, staying
363
- * within the budget block. Omitted/`false` → no line. */
355
+ * onto this domain seat — the deep-tier fold default (pinned diff pack
356
+ * present; SSOT pr-review.md § Review pipeline; prose "collection folded
357
+ * in = seat reuse" = § Review depth default-tier row). Kept-wave
358
+ * exceptions dispatch collect seats instead. Requires a non-empty
359
+ * `diffFile`, and the independent cross-domain security seat is never
360
+ * folded — either contradiction throws. The prompt gains one bullet after
361
+ * the `## Budget` block telling the seat to do its own collection, staying
362
+ * within the budget block. Omitted/`false` → no line. */
364
363
  collectFolded?: boolean;
365
364
  };
366
365
  /**
@@ -370,30 +369,30 @@ export type PrReviewSeatPromptOptions = {
370
369
  * Ingredients:
371
370
  *
372
371
  * - Absolute path to `references/pr-review.md` under `skillRoot` + the
373
- * sections to read; absolute review `worktreePath`.
374
- * - Per-seat budget block (`## Budget`, SP1 tier time-budget): findings /
375
- * evidence-token / file-open caps interpolated from `PR_REVIEW_TIER_BUDGETS`
376
- * — expansion stops for the seat, never a host-level hard stop.
372
+ * sections to read; absolute review `worktreePath`.
373
+ * - Per-seat budget block (`## Budget`, tier time budget): findings /
374
+ * evidence-token / file-open caps interpolated from `PR_REVIEW_TIER_BUDGETS`
375
+ * — expansion stops for the seat, never a host-level hard stop.
377
376
  * - `collectFolded: true` (stage 2 only; stage 1, a missing `diffFile`, or
378
- * the security seat throws): one bullet after the `## Budget` block — the
379
- * collect wave folded onto this seat, so it collects itself (pinned diff
380
- * snapshot/pack first, then in-domain changed files) within the budget
381
- * block.
377
+ * the security seat throws): one bullet after the `## Budget` block — the
378
+ * collect wave folded onto this seat, so it collects itself (pinned diff
379
+ * snapshot/pack first, then in-domain changed files) within the budget
380
+ * block.
382
381
  * - Recon facts + decided tradeoffs.
383
382
  * - Hard Rules 4/5 VERBATIM.
384
383
  * - Payload-return contract (write-blocked-safe; main agent writes files).
385
384
  * - No-verdict / never-post clauses.
386
385
  * - Slug mandate `<domain>-<seat>`.
387
386
  * - Stage 2 adds finding-format.md (+ security-review.md for security
388
- * seats) and the Merge-class instruction.
387
+ * seats) and the Merge-class instruction.
389
388
  * - Tier cuts (SP-A amendment): quick drops the cross-domain /
390
- * independent-security block, the collect-wave wording AND shrinks the
391
- * lens/prompt-ingredient set; default drops the same blocks (SSOT
392
- * pr-review.md § Review depth, default-tier row: "collection folded in =
393
- * seat reuse" — no separate Stage-1 wave, so collect-wave wording is
394
- * deep-only); deep keeps everything.
395
- * Tier omitted → `default` (pr-review.md § Review depth: the no-flag
396
- * landing tier).
389
+ * independent-security block, the collect-wave wording AND shrinks the
390
+ * lens/prompt-ingredient set; default drops the same blocks (SSOT
391
+ * pr-review.md § Review depth, default-tier row: "collection folded in =
392
+ * seat reuse" — no separate Stage-1 wave, so collect-wave wording is
393
+ * deep-only); deep keeps everything.
394
+ * Tier omitted → `default` (pr-review.md § Review depth: the no-flag
395
+ * landing tier).
397
396
  */
398
397
  export declare function prReviewSeatPrompt(opts: PrReviewSeatPromptOptions): string;
399
398
  /** Options for {@link validateFindingDoc}: `prVariant` gates the Merge
@@ -407,18 +406,18 @@ export type ValidateFindingDocOptions = {
407
406
  * PR-only Merge class contract (pr-review.md § Merge class):
408
407
  *
409
408
  * - Every finding opens with `### [CATEGORY-NN] Title`; CATEGORY ∈
410
- * `AUDIT_CATEGORIES` (case-insensitive read, canonical uppercase forms
411
- * per category codes), NN numeric.
409
+ * `AUDIT_CATEGORIES` (case-insensitive read, canonical uppercase forms
410
+ * per category codes), NN numeric.
412
411
  * - Required fields: Evidence / Impact / Effort / Risk / Confidence —
413
- * Effort / Risk / Confidence each validated as their LEADING token
414
- * (Effort via `AUDIT_EFFORTS`, Risk via `AUDIT_RISKS`, Confidence via
415
- * HIGH | MED | LOW with `MEDIUM` tolerated as the MED alias); free-text
416
- * gloss after a separator is allowed and ignored.
412
+ * Effort / Risk / Confidence each validated as their LEADING token
413
+ * (Effort via `AUDIT_EFFORTS`, Risk via `AUDIT_RISKS`, Confidence via
414
+ * HIGH | MED | LOW with `MEDIUM` tolerated as the MED alias); free-text
415
+ * gloss after a separator is allowed and ignored.
417
416
  * - Each Evidence citation matches `path:line` (`\S+:\d+` — the path may
418
- * but need not carry an extension).
417
+ * but need not carry an extension).
419
418
  * - `prVariant` (default false): every finding additionally carries
420
- * **Merge class** ∈ {must-fix, should-fix, nit} placed IMMEDIATELY after
421
- * Confidence.
419
+ * **Merge class** ∈ {must-fix, should-fix, nit} placed IMMEDIATELY after
420
+ * Confidence.
422
421
  */
423
422
  export declare function validateFindingDoc(text: string, opts?: ValidateFindingDocOptions): GateResult;
424
423
  /** Tier keywords the CLI may have matched from argv — the three explicit
@@ -441,16 +440,16 @@ export type ResolvePrReviewTierInput = {
441
440
  * (SP-A amendment; first hit wins):
442
441
  *
443
442
  * 1. Explicit keyword → that tier (user intent beats heuristics; a lone
444
- * `default` returns `default` BEFORE the band/sensitive heuristics).
445
- * Any two DISTINCT keywords (quick / default / deep) → hard-stop
446
- * conflict error — never silently take a priority. (Empty/omitted =
447
- * no flag.)
443
+ * `default` returns `default` BEFORE the band/sensitive heuristics).
444
+ * Any two DISTINCT keywords (quick / default / deep) → hard-stop
445
+ * conflict error — never silently take a priority. (Empty/omitted =
446
+ * no flag.)
448
447
  * 2. Too large (>~1000 / band too-large) → advise split; review anyway →
449
- * deep.
448
+ * deep.
450
449
  * 3. Sensitive surface (auth / LLM / supply chain / data) → deep at any
451
- * size.
450
+ * size.
452
451
  * 4. Large (>~300 / band large) → deep.
453
452
  * 5. Small: tiny-mechanical shape (docs-only / rename / formatting / pure
454
- * deletion) → quick; anything else (real code change) → default.
453
+ * deletion) → quick; anything else (real code change) → default.
455
454
  */
456
455
  export declare function resolvePrReviewTier(input: ResolvePrReviewTierInput): PrReviewTier;
package/dist/roles.d.ts CHANGED
@@ -47,14 +47,14 @@ export type RoleMappingOptions = {
47
47
  * layout (mstar-roles § Role Reference Mapping / § Parameter Table (SSOT)
48
48
  * / § Maintenance Rules):
49
49
  * - every mapped agent id resolves to `references/<role>.md` under
50
- * `rolesDir`;
50
+ * `rolesDir`;
51
51
  * - shared families (`fullstack-dev*`, `qc-specialist*`) resolve to ONE
52
- * shared reference file each;
52
+ * shared reference file each;
53
53
  * - every parameter row references a mapped role, exactly once;
54
54
  * - dev track values are `primary` / `parallel_secondary`;
55
55
  * - the QC parameter table contract holds: reviewer_index is exactly
56
- * {1, 2, 3} across the three seats, each seat has a focus, and
57
- * `report_suffix === qc<reviewer_index>`.
56
+ * {1, 2, 3} across the three seats, each seat has a focus, and
57
+ * `report_suffix === qc<reviewer_index>`.
58
58
  *
59
59
  * Violations:
60
60
  * - `roles.mapping.reference.missing` — mapped reference file not on disk
@@ -70,18 +70,28 @@ export type RoleMappingOptions = {
70
70
  export declare function validateRoleMapping(rolesDir: string, options?: RoleMappingOptions): GateResult;
71
71
  /**
72
72
  * Lint load-order declarations across skill texts (mstar-harness-core
73
- * § 加载约定: every `mstar-*` topic skill presumes the reader has Read core
74
- * first, so each must declare `mstar-harness-core` in its Load Order /
75
- * First action section).
73
+ * § 与其它 `mstar-*` skill 的加载契约 + mstar-roles § Load Order; Spec A2 —
74
+ * single load-selection authority).
76
75
  *
77
76
  * Input: `skillTexts` maps skill name → full SKILL.md text. `mstar-harness-
78
- * core` itself and non-`mstar-*` skills are exempt. Heuristic: a section
79
- * headed Load Order / Load order / First action must exist and mention
80
- * `mstar-harness-core` inside that section (mentions in later sections do
81
- * not count).
77
+ * core` itself and non-`mstar-*` skills are exempt.
78
+ *
79
+ * - Every `mstar-*` topic skill (reached by direct invocation) must have a
80
+ * Load Order / First action section (heading Load Order / Load order /
81
+ * First action) that names `mstar-harness-core` as its first dependency
82
+ * (mentions in later sections do not count).
83
+ * - The `mstar-roles` hub bootstrap is the ONE exception (narrow, keyed on
84
+ * the skill name — broad exemptions are rejected): it does not declare
85
+ * core-first; instead its Load Order section must declare the preset
86
+ * decision matrix (`roles.loadorder.hub.bootstrap.missing` when tokens
87
+ * are missing) and keep the conditional `mstar-harness-core`
88
+ * conflict-authority pointer (`roles.loadorder.core.missing`).
82
89
  *
83
90
  * Violations:
84
91
  * - `roles.loadorder.section.missing` — no Load Order / First action section
85
- * - `roles.loadorder.core.missing` — section exists without the core mention
92
+ * - `roles.loadorder.core.missing` — topic section without the core-first
93
+ * declaration, or hub section without the core conflict-authority pointer
94
+ * - `roles.loadorder.hub.bootstrap.missing` — hub section missing the
95
+ * identity→none/standard/methods/unknown-preset decision matrix
86
96
  */
87
97
  export declare function lintLoadOrder(skillTexts: Record<string, string>): GateResult;
package/dist/sdd.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type GateResult } from "./core.js";
1
2
  /**
2
3
  * Error carrying the ported script exit code so the CLI can map validation
3
4
  * failures to identical non-zero exits.
@@ -18,17 +19,41 @@ export type SddWorkspaceOptions = {
18
19
  /** Working directory for git probes; default `process.cwd()`. */
19
20
  cwd?: string;
20
21
  };
21
- /** Options for `taskBrief` (mirrors `$SDD_DIR` for the default out path). */
22
+ /**
23
+ * Options for `taskBrief` (mirrors `$SDD_DIR` for the default out path).
24
+ *
25
+ * Bound mode (spec A3): passing
26
+ * `context` makes the destination an artifact gate check (before any
27
+ * mkdir/write), defaults the destination to `{context.sddDir}/task-N-brief.md`
28
+ * and returns/emits an absolute path. Without `context` the legacy helper
29
+ * stays explicitly UNBOUND — a context-less call has no protection claim
30
+ * (A3: "context-less legacy helper calls remain explicitly unbound").
31
+ */
22
32
  export type TaskBriefOptions = {
23
33
  sddDir?: string;
34
+ /** Resolved SDD execution context — binds the artifact write (A3). */
35
+ context?: SddExecutionContext;
36
+ /** Observed invocation cwd for the artifact gate; default `process.cwd()`. */
37
+ cwd?: string;
24
38
  };
25
- /** Options for `reviewPackage` (mirrors `$SDD_DIR` + git probe cwd). */
39
+ /**
40
+ * Options for `reviewPackage` (mirrors `$SDD_DIR` + git probe cwd).
41
+ *
42
+ * Bound mode (spec A3): passing `context` makes the destination an artifact
43
+ * gate check (before any mkdir/write), defaults the git probe cwd to the
44
+ * context's feature worktree ("feature Git cwd, control artifact out") and
45
+ * returns/emits an absolute path. Without `context` the legacy helper stays
46
+ * explicitly UNBOUND — no protection claim.
47
+ */
26
48
  export type ReviewPackageOptions = {
27
49
  sddDir?: string;
50
+ /** Git probe cwd; bound mode defaults to `context.featureCwd`. */
28
51
  cwd?: string;
52
+ /** Resolved SDD execution context — binds the artifact write (A3). */
53
+ context?: SddExecutionContext;
29
54
  };
30
55
  /**
31
- * Git capture ceiling for `gitOut` / `reviewPackage` (qc3 W-2): Node's
56
+ * Git capture ceiling for `gitOut` / `reviewPackage` : Node's
32
57
  * default 1 MiB `maxBuffer` ENOBUFS'd on large review ranges. 64 MiB keeps
33
58
  * realistic iteration-close ranges working while bounding memory; captures
34
59
  * beyond that fail as SddScriptError via the CLI.
@@ -39,13 +64,13 @@ export declare const GIT_CAPTURE_MAX_BYTES: number;
39
64
  * the absolute path). Resolution order:
40
65
  *
41
66
  * 1. fail-closed FIRST: a linked worktree without a control root never
42
- * resolves or creates any SDD tree under the feature checkout (refuses a
43
- * second SDD tree; no override or probe may bypass this guard);
67
+ * resolves or creates any SDD tree under the feature checkout (refuses a
68
+ * second SDD tree; no override or probe may bypass this guard);
44
69
  * 2. explicit harness-root override (`opts.harnessDir` / `MSTAR_HARNESS_DIR`)
45
- * — plan finding 2026-08-08: covers repos the status.json probe misses;
46
- * resolved relative to the established root;
70
+ * — plan finding 2026-08-08: covers repos the status.json probe misses;
71
+ * resolved relative to the established root;
47
72
  * 3. `.mstarc` `[config] harness_dir` at `root` (repo-declared root;
48
- * resolved against the config file's directory);
73
+ * resolved against the config file's directory);
49
74
  * 4. `status.json` probe at root (`.mstar` → `.agents`);
50
75
  * 5. fallback: existing `.mstar`/`.agents` dir, else `.mstar`.
51
76
  *
@@ -61,6 +86,14 @@ export declare function sddWorkspace(planId: string, opts?: SddWorkspaceOptions)
61
86
  * EOF for the last task) — a later Task heading resets the section. A
62
87
  * missing task writes an empty file then fails with exit-3
63
88
  * (`SddScriptError.exitCode === 3`).
89
+ *
90
+ * Bound mode (`opts.context`, spec A3): the artifact destination is gated
91
+ * with `checkSddAction` BEFORE any mkdir/write — a refused destination
92
+ * writes nothing — and the returned path is absolute. The INPUT is bound
93
+ * too: the plan file must canonicalize to the context's `planFile`, and a
94
+ * mismatch is refused (exit 1) before any read or write — a foreign plan's
95
+ * content must never land in this plan's SDD dir. Context-less calls
96
+ * keep the legacy unbound behavior (no protection claim).
64
97
  */
65
98
  export declare function taskBrief(planFile: string, taskN: number, outFile?: string, opts?: TaskBriefOptions): string;
66
99
  /**
@@ -69,6 +102,14 @@ export declare function taskBrief(planFile: string, taskN: number, outFile?: str
69
102
  * Both refs are validated with `git rev-parse --verify --quiet` (any ref
70
103
  * the original accepted is accepted here; the SHA-only guard is
71
104
  * `assertBaseSha`).
105
+ *
106
+ * Bound mode (`opts.context`, spec A3): the review range is probed in the
107
+ * context's feature worktree (feature Git cwd) while the package lands in
108
+ * the plan's control artifacts — gated with `checkSddAction` BEFORE any
109
+ * mkdir/write (a refused destination writes nothing). The returned path is
110
+ * absolute. Context-less calls keep the legacy unbound behavior (no
111
+ * protection claim); an explicit `opts.cwd` still overrides the git probe
112
+ * cwd in both modes.
72
113
  */
73
114
  export declare function reviewPackage(base: string, head: string, outFile?: string, opts?: ReviewPackageOptions): string;
74
115
  /**
@@ -130,3 +171,152 @@ export type StickyRulesResult = {
130
171
  * resume — that rule lives in the PM flow, not the session ledger.
131
172
  */
132
173
  export declare function implementerSessionStickyRules(input: StickyRulesInput): StickyRulesResult;
174
+ /**
175
+ * Resolved SDD execution context (spec A3): where control artifacts live,
176
+ * where feature source edits happen, and which branch the feature checkout
177
+ * must be on. All paths normalized absolute (canonicalized on resolve);
178
+ * `planFile` / `sddDir` must resolve within the control harness and match
179
+ * `planId`; the feature branch/worktree must match the verified lease when
180
+ * an active workflow supplies one (standalone non-iteration contexts remain
181
+ * possible under the existing branch policy — no new global lease mandate).
182
+ * A declared control root is authoritative: it is never re-inferred from
183
+ * the feature cwd (mstar-branch-worktree «Harness path SSOT»).
184
+ */
185
+ export type SddExecutionContext = {
186
+ planId: string;
187
+ /** Control harness dir (`<control-worktree>/{HARNESS_DIR}`), absolute. */
188
+ controlHarnessRoot: string;
189
+ /** Feature worktree — the required cwd for product/source edits, absolute. */
190
+ featureCwd: string;
191
+ /** Assignment Working branch checked out at `featureCwd`. */
192
+ workingBranch: string;
193
+ /** Control plan file (`{PLAN_DIR}/<plan-id>.md`), absolute. */
194
+ planFile: string;
195
+ /** Control `{SDD_DIR}` = `{HARNESS_DIR}/sdd/<plan-id>/`, absolute. */
196
+ sddDir: string;
197
+ };
198
+ /** One action seam to gate with `checkSddAction` (spec A3). */
199
+ export type SddAction = {
200
+ /**
201
+ * Observed invocation cwd — the real cwd at the seam, never an Assignment
202
+ * echo. Relative `target` values resolve from this cwd.
203
+ */
204
+ cwd: string;
205
+ /** Path the action would touch; optional for source/launch, required for artifact. */
206
+ target?: string;
207
+ kind: SddActionKind;
208
+ };
209
+ /** Action seam kinds (spec A3): feature source write, control artifact write, child-process launch. */
210
+ export type SddActionKind = "source" | "artifact" | "launch";
211
+ /**
212
+ * Resolve and validate a declared SDD execution context (spec A3) into a
213
+ * canonical context. Read-only — resolves/validates, never writes.
214
+ *
215
+ * Validation (reusing the existing machinery — never duplicated here):
216
+ * - shape: all paths absolute, `planId` a single safe path component
217
+ * (`assertSafePathComponent`);
218
+ * - `controlHarnessRoot` exists (declared root is authoritative — never
219
+ * re-inferred from the feature cwd);
220
+ * - `planFile` identity (basename stem = `planId`) + placement via
221
+ * `assertPlanWritingPath` (inside `{PLAN_DIR}` of the control harness,
222
+ * symlink escape checked against the canonical path);
223
+ * - `sddDir` canonicalizes (nearest existing ancestor on BOTH sides) to the
224
+ * same path as `resolveSddDir(controlHarnessRoot, planId)` — the path
225
+ * SSOT composition, `.mstarc` overrides included; equivalent string forms
226
+ * of one physical destination are valid. Composition equality on the
227
+ * canonical pair subsumes the escape case — a declared sddDir cannot
228
+ * equal the canonical composition and escape at the same time — so
229
+ * divergence is classified at one decision point: divergence because the
230
+ * declared path physically canonicalizes OUTSIDE the control harness
231
+ * (symlinked sdd segment routing out) is environmental →
232
+ * `sdd.context.sdd-dir-escape` gate fail (exit 1); any other divergence
233
+ * (wrong declaration) is usage (exit 2). A context matching a
234
+ * `.mstarc`-declared sdd base is honored wherever the repo's own path
235
+ * SSOT composes it — the engine never second-guesses a composition it
236
+ * would itself produce (`resolveSddDir` is authoritative);
237
+ * - `featureCwd` exists and never nests with the control checkout
238
+ * (`featureCwd` inside the control checkout, or the control harness
239
+ * inside the feature checkout, are both refused — L1 hard rules);
240
+ * - branch/lease: when the control harness's workflow snapshots supply a
241
+ * plan row from a REGISTERED ACTIVE workflow (v2 root `status.json`
242
+ * `workflows[]`; a retained terminal snapshot never satisfies lease
243
+ * enforcement, and a plan claimed by multiple active workflows fails
244
+ * closed), its lease is verified (`verifyPlanExecutionLease`) and the
245
+ * L1 checklist runs (`l1PreDispatchCheck` with the control checkout);
246
+ * the context must then match the verified lease exactly. Without an
247
+ * active lease (no row, or a non-InProgress row without lease), the
248
+ * standalone branch policy applies (`assertBranchAlignment`) — an
249
+ * InProgress row without lease is the orphan refusal.
250
+ *
251
+ * Throws `SddScriptError` — exit 2 when the declared context itself is
252
+ * malformed (non-absolute path, identity/composition mismatch, missing plan
253
+ * file), exit 1 when the environment fails the gate (missing dirs, branch
254
+ * mismatch, lease/orphan refusal, symlink escape). A rejected context never
255
+ * reaches an action check.
256
+ */
257
+ export declare function resolveSddExecutionContext(input: SddExecutionContext): SddExecutionContext;
258
+ /**
259
+ * Gate one action seam against a resolved context (spec A3). Read-only —
260
+ * a refused action performs no write and the check itself never writes.
261
+ *
262
+ * - `kind: "source"` — the observed `cwd` must sit inside the feature
263
+ * worktree (nested directories allowed); a relative `target` resolves from
264
+ * that actual cwd. Targets outside the feature — traversal, absolute
265
+ * elsewhere, wrong-cwd, or symlink escape — are refused before mutation.
266
+ * - `kind: "artifact"` — the `target` must stay inside the plan's control
267
+ * `sddDir` or equal the declared `planFile`; legitimate control artifact
268
+ * edits are allowed while arbitrary control source edits are not. A
269
+ * nonexistent leaf canonicalizes through its nearest existing ancestor
270
+ * (`canonicalizeNearestExisting`); symlink escapes are refused.
271
+ * - `kind: "launch"` — verifies the resolved launch destination
272
+ * `featureCwd` (exists + on `workingBranch` via the reused
273
+ * `assertBranchAlignment`); the parent's own cwd is not gated, because a
274
+ * launch may legitimately run from control/main — its purpose is to bind
275
+ * the child's starting cwd to the feature worktree. An optional `target`
276
+ * is checked as a source target relative to `featureCwd` (where the child
277
+ * will start).
278
+ *
279
+ * Violations minted here carry the `sdd.context.*` prefix; violations from
280
+ * the reused branch/lease helpers keep their own codes (`worktree.*`,
281
+ * `lease.*`, `plan-path.*`) — the rules stay single-sourced. A3 limits
282
+ * apply: this is a snapshot check, not a future-write lock, and offers no
283
+ * protection against a concurrent hostile symlink swap.
284
+ *
285
+ * API contract (task-1 review Minor 2): `context` must be a
286
+ * `resolveSddExecutionContext`-produced (or equivalently already-validated)
287
+ * context. This function gates the action seam only and does NOT
288
+ * re-validate the context declaration — identity, branch, lease and
289
+ * control/feature nesting checks run at resolve time — so a hand-assembled
290
+ * structurally-valid context gets no protection claim from this check
291
+ * alone.
292
+ */
293
+ export declare function checkSddAction(context: SddExecutionContext, action: SddAction): GateResult;
294
+ /**
295
+ * Bound argv launcher (spec A3):
296
+ * resolve + gate the context, then spawn a DIRECT executable argv — never
297
+ * a shell (no command-string interpolation; the argv array reaches the
298
+ * child literally, spaces/`$()`/backticks unchanged) — with
299
+ * cwd = the resolved feature worktree, inherited stdio and the inherited
300
+ * process environment unchanged (no HOME/CODEX_HOME edits, no credential
301
+ * copies). The parent's own cwd is deliberately not gated: a launch may
302
+ * run from control/main, and the `launch` seam validates the resolved
303
+ * launch destination (featureCwd + workingBranch) instead.
304
+ *
305
+ * Exit contract (Task 2 CLI mapping):
306
+ * - empty/invalid argv → `SddScriptError` exit 2 (usage);
307
+ * - context/gate failure → `SddScriptError` exit 1 (usage-class declaration
308
+ * errors from resolution keep their own exit 2);
309
+ * - spawn-not-found (ENOENT) → resolves 127;
310
+ * - numeric child exit → resolved unchanged (exit 7 returns 7);
311
+ * - child killed by signal n → resolves 128+n (SIGTERM → 143, SIGINT → 130).
312
+ *
313
+ * SIGINT/SIGTERM are forwarded to the running child; the listeners are
314
+ * removed once the child settles — the launcher leaves no handlers behind.
315
+ *
316
+ * A3 limits apply unchanged: the launcher binds the child's STARTING cwd;
317
+ * it is not a sandbox — a child can later chdir, pass an overriding cwd
318
+ * flag, write absolute paths elsewhere, or use host edit tooling. Other
319
+ * spawn errors (e.g. EACCES) reject; the CLI maps them to exit 1 with the
320
+ * cause in the message.
321
+ */
322
+ export declare function runInSddContext(context: SddExecutionContext, argv: readonly string[]): Promise<number>;