@mstar-harness/engine 3.6.3 → 3.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.d.ts +11 -12
- package/dist/compound.d.ts +10 -10
- package/dist/core.d.ts +2 -3
- package/dist/dispatch.d.ts +57 -64
- package/dist/engine.js +820 -133
- package/dist/gates.d.ts +69 -0
- package/dist/gates.test.d.ts +1 -0
- package/dist/index.d.ts +13 -8
- package/dist/iteration.d.ts +3 -3
- package/dist/lint.d.ts +49 -49
- package/dist/migrate.d.ts +22 -22
- package/dist/path.d.ts +35 -25
- package/dist/project.d.ts +24 -26
- package/dist/prreview.d.ts +98 -99
- package/dist/roles.d.ts +22 -12
- package/dist/sdd.d.ts +198 -8
- package/dist/skill-authoring.d.ts +36 -9
- package/dist/status.d.ts +10 -11
- package/dist/store.d.ts +9 -12
- package/dist/workflow.d.ts +11 -11
- package/dist/worktree.d.ts +30 -6
- package/package.json +1 -1
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
|
-
/**
|
|
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
|
-
/**
|
|
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`
|
|
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
|
-
*
|
|
43
|
-
*
|
|
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
|
-
*
|
|
46
|
-
*
|
|
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
|
-
*
|
|
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>;
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* Source skills (semantic SSOT — this module implements their deterministic
|
|
7
7
|
* rules, it never redefines them; roadmap §8.5 C2):
|
|
8
8
|
* - `mstar-skill-authoring` SKILL.md § Frontmatter Contract — `name` stable
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* lowercase-hyphen; `description` is the trigger contract (not a workflow
|
|
10
|
+
* summary), third person.
|
|
11
11
|
* - `mstar-skill-authoring` SKILL.md § Body 必须回答的 5 问 + § 默认 Body
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* 结构 — a SKILL.md body answers five questions via key sections (Load
|
|
13
|
+
* Order, Workflow, Decision Rules, Evidence, References).
|
|
14
14
|
* - `mstar-skill-authoring` SKILL.md § Skill-relative script and asset
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* paths ("skill `my-skill` → scripts/do-thing") + `mstar-host` SKILL.md
|
|
16
|
+
* § Resolve loaded skill root (per-host resolution).
|
|
17
17
|
*
|
|
18
18
|
* The SkillsBench six principles and trigger-contract reasoning stay prompt.
|
|
19
19
|
*/
|
|
@@ -46,10 +46,37 @@ export declare function stripFrontmatter(text: string): string;
|
|
|
46
46
|
* or `runtime` (canonical plus the locked `RUNTIME_HEADING_ALIASES` table
|
|
47
47
|
* — shipped `mstar-*` topic skills). */
|
|
48
48
|
export type FiveQuestionMode = "authoring" | "runtime";
|
|
49
|
+
/** Skill-lint profile kind (spec A4): `core` = the `mstar-harness-core` hub
|
|
50
|
+
* (five-question exempt), `runtime` = shipped `mstar-*` topic skills, and
|
|
51
|
+
* `authoring` = the standard plus every non-`mstar-*`/unknown input. */
|
|
52
|
+
export type SkillLintKind = "core" | "runtime" | "authoring";
|
|
53
|
+
/** Classification result: the profile kind plus the five-question mode to
|
|
54
|
+
* apply — `null` mode means the five-question check is skipped entirely
|
|
55
|
+
* (core exemption only; every other check stays active). */
|
|
56
|
+
export type SkillLintProfile = {
|
|
57
|
+
kind: SkillLintKind;
|
|
58
|
+
mode: FiveQuestionMode | null;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* One lint-classification policy (spec A4
|
|
62
|
+
* the single profile SSOT the CLI, dsh and drift Guard 5 consume.
|
|
63
|
+
* Semantics preserve the CLI `skill lint` selection verbatim:
|
|
64
|
+
* - exact `mstar-harness-core` → `core` / `null` (five-question exempt by
|
|
65
|
+
* design — hub headings; frontmatter + ephemeral checks still run);
|
|
66
|
+
* - exact `mstar-skill-authoring` → `authoring` (the standard's own
|
|
67
|
+
* definition stays strict despite the `mstar-` prefix);
|
|
68
|
+
* - any other `mstar-*` → `runtime` (locked alias table applies);
|
|
69
|
+
* - unknown / non-`mstar-*` / missing identity → `authoring` (strict
|
|
70
|
+
* default — greenfield inputs are never loosened).
|
|
71
|
+
* The `skillId` is the resolved target directory basename at the CLI and
|
|
72
|
+
* dsh configured skill-root boundaries — never an arbitrary YAML `name`
|
|
73
|
+
* alone (a misleading frontmatter name must not select a looser profile).
|
|
74
|
+
* These are lint profiles only: not authorization, not trusted origin.
|
|
75
|
+
*/
|
|
76
|
+
export declare function classifySkillLint(skillId: string | undefined): SkillLintProfile;
|
|
49
77
|
/**
|
|
50
|
-
* Locked runtime alias table
|
|
51
|
-
*
|
|
52
|
-
* shipped topic skills, verified against the corpus at `81480e7`. Tokens are
|
|
78
|
+
* Locked runtime alias table: heading synonyms that answer the same question
|
|
79
|
+
* for shipped topic skills, verified against the shipped-skill corpus. Tokens are
|
|
53
80
|
* case-insensitive heading substrings (any heading level); the `decision-rules`
|
|
54
81
|
* breadth is bounded by the corpus regression test pinning current state.
|
|
55
82
|
* `load-order` has no aliases — the canonical label covers 15/16 skills and
|
package/dist/status.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type StatusDoc = {
|
|
|
14
14
|
[key: string]: unknown;
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
|
-
* v2 root status document (`{HARNESS_DIR}/status.json
|
|
17
|
+
* v2 root status document (`{HARNESS_DIR}/status.json`( — hard
|
|
18
18
|
* cutover): `version`, `updated_at`, `workflows[]` only. The list holds
|
|
19
19
|
* ACTIVE (non-terminal) lifecycles; terminal writers unregister AFTER the
|
|
20
20
|
* snapshot write (removal-at-terminal).
|
|
@@ -96,7 +96,7 @@ export declare function validatePlanRow(row: unknown): GateResult;
|
|
|
96
96
|
*/
|
|
97
97
|
export declare function validateResidual(entry: unknown): GateResult;
|
|
98
98
|
/**
|
|
99
|
-
* Validate one v2 root `workflows[]` entry (
|
|
99
|
+
* Validate one v2 root `workflows[]` entry (): required `id`,
|
|
100
100
|
* `type` (plan | iteration), `started_at`, `dir` — harness-relative, never
|
|
101
101
|
* absolute and never containing `..`. The removal-at-terminal invariant
|
|
102
102
|
* (snapshot exists and is non-terminal) is checked at document level by
|
|
@@ -104,7 +104,7 @@ export declare function validateResidual(entry: unknown): GateResult;
|
|
|
104
104
|
*/
|
|
105
105
|
export declare function validateWorkflowEntry(entry: unknown): GateResult;
|
|
106
106
|
/**
|
|
107
|
-
* Validate a v2 status.json document (
|
|
107
|
+
* Validate a v2 status.json document ( — hard cutover). Accepts a
|
|
108
108
|
* parsed document or a file path (malformed JSON yields a
|
|
109
109
|
* `status.invalid-json` violation, never a throw). v1 or unknown-version
|
|
110
110
|
* inputs — including v1-shaped documents carrying a root `plans[]` — fail
|
|
@@ -122,14 +122,14 @@ export declare function validateWorkflowEntry(entry: unknown): GateResult;
|
|
|
122
122
|
* root holds active lifecycles only, and terminal writers unregister AFTER
|
|
123
123
|
* the snapshot write. The snapshot must also PHYSICALLY live under the
|
|
124
124
|
* harness: a symlinked `workflows/<id>/` (or snapshot file) resolving
|
|
125
|
-
* outside the harness dir is rejected fail-closed
|
|
125
|
+
* outside the harness dir is rejected fail-closed . Doc
|
|
126
126
|
* input without a harness dir is structure-only.
|
|
127
127
|
*/
|
|
128
128
|
export declare function validateStatusV2(docOrPath: StatusV2Doc | string, opts?: {
|
|
129
129
|
harnessDir?: string;
|
|
130
130
|
}): GateResult;
|
|
131
131
|
/**
|
|
132
|
-
* Relocated v2 root validator (
|
|
132
|
+
* Relocated v2 root validator ( — hard cutover, no dual path):
|
|
133
133
|
* the v1 `validateStatus` implementation was deleted in the same task that
|
|
134
134
|
* introduced the v2 surface; the public export name survives so external
|
|
135
135
|
* consumers (CLI, host hooks — cut over in P2) keep compiling and now fail
|
|
@@ -158,14 +158,14 @@ export declare const validateStatus: typeof validateStatusV2;
|
|
|
158
158
|
* Async-only (architect-locked 2026-08-27): the durable write goes through
|
|
159
159
|
* `getArtifactStore().put({ kind: "status", key: "root", ... })` inside the
|
|
160
160
|
* caller's lock — the store is the persist backend, never a second lock.
|
|
161
|
-
* Fails loud
|
|
161
|
+
* Fails loud when the active FsStore would resolve its
|
|
162
162
|
* `status.json` to a path other than `statusPath` — callers whose root
|
|
163
163
|
* differs from the active store's root MUST
|
|
164
164
|
* `setArtifactStore(createFsStore(root))` first.
|
|
165
165
|
*/
|
|
166
166
|
export declare function registerWorkflowEntryLocked(statusPath: string, entry: WorkflowEntry): Promise<StatusV2Doc>;
|
|
167
167
|
/**
|
|
168
|
-
* Register one active workflow entry in the v2 root file (
|
|
168
|
+
* Register one active workflow entry in the v2 root file ().
|
|
169
169
|
* Idempotent upsert by entry `id` under the root-file `withStatusWriteLock`,
|
|
170
170
|
* bumping root `updated_at`. A missing/empty root file is initialized from
|
|
171
171
|
* the v2 template (never a v1 tree); a v1 root is refused with the
|
|
@@ -178,7 +178,7 @@ export declare function registerWorkflowEntryLocked(statusPath: string, entry: W
|
|
|
178
178
|
*/
|
|
179
179
|
export declare function registerWorkflow(root: string, entry: WorkflowEntry): Promise<StatusV2Doc>;
|
|
180
180
|
/**
|
|
181
|
-
* Remove one workflow entry from the v2 root file (
|
|
181
|
+
* Remove one workflow entry from the v2 root file (). Idempotent:
|
|
182
182
|
* removing an absent id is a no-op with no write; a missing/empty root file
|
|
183
183
|
* is a no-op that never creates the file. Runs under the root-file
|
|
184
184
|
* `withStatusWriteLock`, bumping root `updated_at` only when an entry was
|
|
@@ -186,7 +186,7 @@ export declare function registerWorkflow(root: string, entry: WorkflowEntry): Pr
|
|
|
186
186
|
* invariant included) before the write — a v1 root is refused with the
|
|
187
187
|
* `mstar migrate` hint.
|
|
188
188
|
*
|
|
189
|
-
* Fails loud
|
|
189
|
+
* Fails loud when the active FsStore would resolve its
|
|
190
190
|
* `status.json` to a path other than the caller's root — the no-op branches
|
|
191
191
|
* below never mask a store/path mismatch.
|
|
192
192
|
*/
|
|
@@ -199,8 +199,7 @@ export declare function unregisterWorkflow(root: string, id: string): Promise<St
|
|
|
199
199
|
* frontmatter declares `enforcement: hard` hardens the gate in this repo.
|
|
200
200
|
* A COMPLETED (or status-less/archived) iteration's compass NEVER hardens:
|
|
201
201
|
* D2 rollback = unset the flag in the ACTIVE compass, and that must work
|
|
202
|
-
* while older completed compasses still declare hard
|
|
203
|
-
* A counting compass declaring a non-hard value, or no compass at all,
|
|
202
|
+
* while older completed compasses still declare hard. * A counting compass declaring a non-hard value, or no compass at all,
|
|
204
203
|
* leaves the flag unset (`source: none`) — hard gates are never the default
|
|
205
204
|
* and the flag is inert when the engine is absent. Frontmatter is
|
|
206
205
|
* `---`-fenced; hard declarations in the compass BODY do not count (the
|
package/dist/store.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
/** JSON coordination-doc kinds the store persists
|
|
2
|
-
export type ArtifactKind = "status" | "snapshot" | "residuals" | "review" | "json";
|
|
1
|
+
/** JSON coordination-doc kinds the store persists */ export type ArtifactKind = "status" | "snapshot" | "residuals" | "review" | "json";
|
|
3
2
|
/** Stable key inside the kind. Workflow id, project id, or review id;
|
|
4
3
|
* `kind: "status"` always uses key `"root"`. */
|
|
5
4
|
export type ArtifactRef = {
|
|
@@ -23,14 +22,13 @@ export interface ArtifactStore {
|
|
|
23
22
|
get<T = unknown>(ref: ArtifactRef): Promise<T | undefined>;
|
|
24
23
|
delete?(ref: ArtifactRef): Promise<void>;
|
|
25
24
|
/** Enumerate refs of `kind`, sorted by key ascending (spec D4). Uniform
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
* rule: report what exists — missing backing dir/file → `[]`; every
|
|
26
|
+
* listed key round-trips through `get`. `json` is not enumerable. */
|
|
28
27
|
list?(kind: ArtifactKind): Promise<ArtifactRef[]>;
|
|
29
28
|
}
|
|
30
|
-
/** Map an artifact ref to its file path under `harnessRoot` (
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* imports the contract instead of re-deriving it textually. */
|
|
29
|
+
/** Map an artifact ref to its file path under `harnessRoot` (the store contract — the single kind→path
|
|
30
|
+
* mapping). Exported so host adapters import the contract instead of
|
|
31
|
+
* re-deriving it textually. */
|
|
34
32
|
export declare function resolveArtifactPath(harnessRoot: string, ref: ArtifactRef): string;
|
|
35
33
|
/** Default local adapter: maps kinds to the existing `.mstar/` paths.
|
|
36
34
|
* `put` uses the sync `writeJson` (atomic temp+rename unchanged) and
|
|
@@ -39,7 +37,7 @@ export declare function resolveArtifactPath(harnessRoot: string, ref: ArtifactRe
|
|
|
39
37
|
* malformed JSON → throw with the path in the message. The returned store
|
|
40
38
|
* also exposes its resolved `root` so the routed writers can fail loud
|
|
41
39
|
* when a caller's explicit target path diverges from the store-resolved
|
|
42
|
-
* path
|
|
40
|
+
* path ; `root` is not part of the `ArtifactStore` contract.
|
|
43
41
|
* `list` enumerates per the D4 table: report what exists — missing
|
|
44
42
|
* backing dir/file → `[]`, `json` throws, keys sorted ascending. */
|
|
45
43
|
export declare function createFsStore(harnessRoot: string): ArtifactStore & {
|
|
@@ -52,7 +50,7 @@ export declare function setArtifactStore(store: ArtifactStore | undefined): void
|
|
|
52
50
|
* `resolveHarnessSubdir` — never a silent cwd fallback. */
|
|
53
51
|
export declare function getArtifactStore(): ArtifactStore;
|
|
54
52
|
/**
|
|
55
|
-
* Fail-loud path-agreement guard for the routed writers
|
|
53
|
+
* Fail-loud path-agreement guard for the routed writers : when
|
|
56
54
|
* `store` is an FsStore, resolve the path the store would compute for
|
|
57
55
|
* `ref` and require it to equal `expectedPath` (the caller's explicit
|
|
58
56
|
* target). A divergence means the caller's path lives outside the active
|
|
@@ -62,8 +60,7 @@ export declare function getArtifactStore(): ArtifactStore;
|
|
|
62
60
|
* Cheap: pure path resolution, no I/O.
|
|
63
61
|
*/
|
|
64
62
|
export declare function assertFsStorePath(store: ArtifactStore, ref: ArtifactRef, expectedPath: string): void;
|
|
65
|
-
/** Load a store module from a filesystem path (
|
|
66
|
-
* SP2-AC6 / SP2-AC7). Resolves against cwd; rejects empty values and any
|
|
63
|
+
/** Load a store module from a filesystem path (the store module-loading trust boundary). Resolves against cwd; rejects empty values and any
|
|
67
64
|
* URI scheme before `import()`; throws when the file is missing. Accepts a
|
|
68
65
|
* `createArtifactStore` named export, a default-exported factory, or a
|
|
69
66
|
* default-exported object; the result is structurally verified (`put` +
|
package/dist/workflow.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { GateResult } from "./core.js";
|
|
2
2
|
import { type IntegrationMergeLease } from "./lease.js";
|
|
3
3
|
import { type PlanRow } from "./status.js";
|
|
4
|
-
/** Snapshot file name inside `workflows/<id>/` (
|
|
4
|
+
/** Snapshot file name inside `workflows/<id>/` ( — writer contract). */
|
|
5
5
|
export declare const WORKFLOW_SNAPSHOT_FILE = "snapshot.json";
|
|
6
|
-
/** Lifecycle status enum (
|
|
6
|
+
/** Lifecycle status enum ( — terminal set = completed|failed|stopped). */
|
|
7
7
|
export declare const WORKFLOW_LIFECYCLE_STATUSES: readonly ["running", "paused", "completed", "failed", "stopped"];
|
|
8
8
|
/** Terminal statuses: snapshot must carry `ended_at` and no dangling leases. */
|
|
9
9
|
export declare const WORKFLOW_TERMINAL_STATUSES: readonly ["completed", "failed", "stopped"];
|
|
10
|
-
/** Lifecycle type enum (
|
|
10
|
+
/** Lifecycle type enum ( — id reuses the orchestration id). */
|
|
11
11
|
export declare const WORKFLOW_LIFECYCLE_TYPES: readonly ["plan", "iteration"];
|
|
12
12
|
export type WorkflowLifecycleStatus = (typeof WORKFLOW_LIFECYCLE_STATUSES)[number];
|
|
13
13
|
export type WorkflowLifecycleType = (typeof WORKFLOW_LIFECYCLE_TYPES)[number];
|
|
14
14
|
/**
|
|
15
|
-
* First-class lifecycle execution policy (
|
|
15
|
+
* First-class lifecycle execution policy ( — keys copied from root
|
|
16
16
|
* `metadata` at migrate; values accepted-but-opaque this iteration, no
|
|
17
17
|
* semantic gate).
|
|
18
18
|
*/
|
|
@@ -21,7 +21,7 @@ export type WorkflowExecutionPolicy = {
|
|
|
21
21
|
worktree_mode?: unknown;
|
|
22
22
|
push_policy?: unknown;
|
|
23
23
|
};
|
|
24
|
-
/** Iteration branch anchors (
|
|
24
|
+
/** Iteration branch anchors ( — from root metadata anchors). */
|
|
25
25
|
export type WorkflowBranchAnchors = {
|
|
26
26
|
base?: string;
|
|
27
27
|
integration?: string;
|
|
@@ -29,11 +29,11 @@ export type WorkflowBranchAnchors = {
|
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
31
|
* v3 workflow snapshot (`workflows/<id>/snapshot.json`) — final schema
|
|
32
|
-
* (
|
|
32
|
+
* (). `plans[]` rows are the legacy PlanRow shape verbatim;
|
|
33
33
|
* per-row `execution_lease` stays on the row, `integration_merge_lease` is
|
|
34
34
|
* top-level.
|
|
35
35
|
*
|
|
36
|
-
* Notes dual-home SSOT
|
|
36
|
+
* Notes dual-home SSOT: a plan row's `notes` array is the
|
|
37
37
|
* LEGACY VERBATIM copy preserved at migrate time — the RUNTIME ledger is
|
|
38
38
|
* `notes.jsonl` in the workflow dir (`migrate.ts` NOTES_LEDGER_FILE). New
|
|
39
39
|
* notes append to the ledger only; row `notes` is read-only legacy and is
|
|
@@ -57,7 +57,7 @@ export type WorkflowSnapshot = {
|
|
|
57
57
|
compass_ref?: string;
|
|
58
58
|
};
|
|
59
59
|
/**
|
|
60
|
-
* Validate a v3 workflow snapshot document (
|
|
60
|
+
* Validate a v3 workflow snapshot document ( — final schema):
|
|
61
61
|
* enum/type/id checks, `schema_version: 1`, required timestamps, `plans[]`
|
|
62
62
|
* rows validated by the legacy `validatePlanRow` with row-level
|
|
63
63
|
* `execution_lease` shape delegated to `validateExecutionLease`,
|
|
@@ -70,16 +70,16 @@ export type WorkflowSnapshot = {
|
|
|
70
70
|
export declare function validateWorkflowSnapshot(doc: unknown): GateResult;
|
|
71
71
|
/**
|
|
72
72
|
* Write a workflow snapshot as a whole-rewrite of `dir/snapshot.json` under
|
|
73
|
-
* `withStatusWriteLock(snapshotPath)` (
|
|
73
|
+
* `withStatusWriteLock(snapshotPath)` ( — the `.status-write.lockdir`
|
|
74
74
|
* lands inside `workflows/<id>/`, dirname of the snapshot; no harness-root
|
|
75
75
|
* pollution). The snapshot is validated first — an invalid snapshot throws
|
|
76
76
|
* and nothing is written. `dir` is created recursively. The durable write
|
|
77
|
-
* routes through the active `ArtifactStore` (
|
|
77
|
+
* routes through the active `ArtifactStore` (the store contract: snapshot →
|
|
78
78
|
* `{ kind: "snapshot", key: <workflow id> }`) inside the existing lock — the
|
|
79
79
|
* default FsStore resolves `{WORKFLOW_DIR}/<key>/snapshot.json`, identical
|
|
80
80
|
* to `join(dir, WORKFLOW_SNAPSHOT_FILE)` for canonical callers. The write
|
|
81
81
|
* fails loud when the active FsStore would resolve a different path than
|
|
82
|
-
* the caller's `join(dir, WORKFLOW_SNAPSHOT_FILE)`
|
|
82
|
+
* the caller's `join(dir, WORKFLOW_SNAPSHOT_FILE)` — callers
|
|
83
83
|
* whose target root differs from the active store's root MUST
|
|
84
84
|
* `setArtifactStore(createFsStore(root))` first.
|
|
85
85
|
*/
|
package/dist/worktree.d.ts
CHANGED
|
@@ -38,8 +38,7 @@ export type BranchProbeOptions = {
|
|
|
38
38
|
/**
|
|
39
39
|
* Git probe timeout in ms (default 10s; `MSTAR_GIT_PROBE_TIMEOUT_MS` env
|
|
40
40
|
* overrides; per-call value wins). On timeout the probe fails closed into
|
|
41
|
-
* `branch-probe-failed` — never hangs, never guesses a branch
|
|
42
|
-
*/
|
|
41
|
+
* `branch-probe-failed` — never hangs, never guesses a branch. */
|
|
43
42
|
timeoutMs?: number;
|
|
44
43
|
};
|
|
45
44
|
/**
|
|
@@ -56,6 +55,27 @@ export type QcSnapshotAssignment = QcAlignmentAssignment & {
|
|
|
56
55
|
/** Precomputed review HEAD (full SHA preferred) for that assignment. */
|
|
57
56
|
head?: string;
|
|
58
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* True when `candidatePath` is a Git checkout DISTINCT from `controlPath` —
|
|
60
|
+
* the canonical per-worktree git dirs differ. A linked worktree from
|
|
61
|
+
* `git worktree add` (nested inside the control checkout or a sibling) has
|
|
62
|
+
* its own git dir and is distinct; the same checkout, a plain subdirectory
|
|
63
|
+
* of it, or a symlink alias of it resolves to the same git dir and is NOT
|
|
64
|
+
* distinct. Everything unprovable — a non-repo path or a probe failure —
|
|
65
|
+
* is NOT distinct: fail closed, never guess an identity.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isDistinctCheckout(controlPath: string, candidatePath: string, opts?: BranchProbeOptions): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Probe the repository top-level (worktree root) of a checkout via
|
|
70
|
+
* `git -C <path> rev-parse --show-toplevel` — bounded exactly like
|
|
71
|
+
* `probeBranch` / `probeCheckout`, fail-closed (null on any failure). The
|
|
72
|
+
* canonical top-level is the checkout root regardless of where inside it
|
|
73
|
+
* the probed path sits — a `.mstarc`-declared nested harness dir like
|
|
74
|
+
* `<control>/state/.mstar` included — so callers never infer the checkout
|
|
75
|
+
* root from `dirname(harness)` (a layout assumption that only holds when
|
|
76
|
+
* the harness sits directly under the checkout).
|
|
77
|
+
*/
|
|
78
|
+
export declare function probeCheckoutRoot(path: string, opts?: BranchProbeOptions): string | null;
|
|
59
79
|
/**
|
|
60
80
|
* L1 cross-plan pre-dispatch checklist (mstar-branch-worktree L1 table +
|
|
61
81
|
* Harness path SSOT hard rules): control path recorded, feature worktree
|
|
@@ -75,11 +95,15 @@ export declare function l1PreDispatchCheck(input: L1PreDispatchInput, opts?: Bra
|
|
|
75
95
|
export declare function l2PreDispatchCheck(input: L2PreDispatchInput, opts?: BranchProbeOptions): GateResult;
|
|
76
96
|
/**
|
|
77
97
|
* L1 hard rule (Harness path SSOT): `execution_lease.worktree_path` MUST
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
98
|
+
* be a Git checkout DISTINCT from `metadata.control_worktree_path` — the
|
|
99
|
+
* same checkout, a plain subdirectory, or a symlink alias of the control
|
|
100
|
+
* checkout is refused (checkout identity via the canonical per-worktree
|
|
101
|
+
* git dir; probe failure fails closed). Both-empty stays a match (nothing
|
|
102
|
+
* recorded, per the lease validator contract); one empty has nothing to
|
|
103
|
+
* compare and passes (the lease validator's absolute-path requirement owns
|
|
104
|
+
* empty lease paths).
|
|
81
105
|
*/
|
|
82
|
-
export declare function assertControlVsFeaturePath(controlWorktreePath: string, featureWorktreePath: string): GateResult;
|
|
106
|
+
export declare function assertControlVsFeaturePath(controlWorktreePath: string, featureWorktreePath: string, opts?: BranchProbeOptions): GateResult;
|
|
83
107
|
/**
|
|
84
108
|
* Assert the branch checked out at `worktreePath` matches `expectedBranch`
|
|
85
109
|
* (the Assignment Working branch). Probe = `git -C <path> branch
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mstar-harness/engine",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.1",
|
|
4
4
|
"description": "Morning Star Harness Workflow Engine — deterministic workflow enforcement library (path, status, lease, dispatch, sdd, iteration, lint gates).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|