@mstar-harness/engine 0.0.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/README.md +27 -0
- package/dist/audit.d.ts +99 -0
- package/dist/compound.d.ts +90 -0
- package/dist/core.d.ts +112 -0
- package/dist/design-md.d.ts +113 -0
- package/dist/dispatch.d.ts +205 -0
- package/dist/engine.js +3439 -0
- package/dist/host.d.ts +95 -0
- package/dist/index.d.ts +52 -0
- package/dist/iteration.d.ts +88 -0
- package/dist/lease.d.ts +193 -0
- package/dist/lint.d.ts +206 -0
- package/dist/path.d.ts +117 -0
- package/dist/roles.d.ts +87 -0
- package/dist/sdd.d.ts +123 -0
- package/dist/skill-authoring.d.ts +60 -0
- package/dist/status.d.ts +168 -0
- package/dist/worktree.d.ts +104 -0
- package/package.json +41 -0
package/dist/path.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type ValidationResult } from "./core.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options for `resolveHarnessDir`.
|
|
4
|
+
*/
|
|
5
|
+
export type ResolveHarnessDirOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* Explicit harness root. Resolved against `startDir` when relative.
|
|
8
|
+
* Takes precedence over `MSTAR_HARNESS_DIR` and over default probing.
|
|
9
|
+
* Authoritative: the path is returned even when it does not exist yet
|
|
10
|
+
* (the caller may scaffold it).
|
|
11
|
+
*/
|
|
12
|
+
harnessDir?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Resolve `{HARNESS_DIR}` per plan-conventions § {HARNESS_DIR} 解析顺序
|
|
16
|
+
* (find-first-stop): `.mstar/` → `.agents/` → `.plans/`/`plans/`, walking up
|
|
17
|
+
* from `startDir`. Harness candidates are dir-existence (the empty-dir rule
|
|
18
|
+
* applies to `{SPECS_DIR}` only). An explicit override via `opts.harnessDir`
|
|
19
|
+
* or `MSTAR_HARNESS_DIR` wins over probing.
|
|
20
|
+
*
|
|
21
|
+
* Returns the absolute harness dir, or `null` when no candidate exists.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveHarnessDir(startDir?: string, opts?: ResolveHarnessDirOptions): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Options for `resolveSpecsDir`.
|
|
26
|
+
*/
|
|
27
|
+
export type ResolveSpecsDirOptions = {
|
|
28
|
+
/**
|
|
29
|
+
* Default true: when every candidate is absent or empty, create
|
|
30
|
+
* `{HARNESS_DIR}/specs/` (plan-conventions § 创建默认). Read-only callers
|
|
31
|
+
* (e.g. `mstar path resolve`) pass `false` to skip the side effect.
|
|
32
|
+
*/
|
|
33
|
+
create?: boolean;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Resolve `{SPECS_DIR}` per plan-conventions § {SPECS_DIR} 解析: first
|
|
37
|
+
* non-empty candidate wins — `{HARNESS_DIR}/specs/` → `docs/specs/` →
|
|
38
|
+
* repo-root `specs/` (repo root = parent of the harness dir), then the
|
|
39
|
+
* legacy read-only `designs/` candidates (`{HARNESS_DIR}/designs/` →
|
|
40
|
+
* repo-root `designs/`, § {SPECS_DIR} 解析 Legacy — 兼容读 only, never
|
|
41
|
+
* created by init). A candidate that exists but holds no files is treated
|
|
42
|
+
* as absent (empty-dir rule, recursive). When all candidates are absent,
|
|
43
|
+
* `{HARNESS_DIR}/specs/` is created and returned (unless `create: false`).
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveSpecsDir(harnessDir: string, opts?: ResolveSpecsDirOptions): string;
|
|
46
|
+
/**
|
|
47
|
+
* Compose `{PLAN_DIR}` from the harness dir (plan-conventions § 路径符号).
|
|
48
|
+
* Legacy layout: when the harness root is a plans dir itself (`.plans/` or
|
|
49
|
+
* `plans/`, resolution rung 3), `{HARNESS_DIR}={PLAN_DIR}` — the same
|
|
50
|
+
* directory is returned.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolvePlanDir(harnessDir: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* Single safe path component for per-plan path composition
|
|
55
|
+
* (qc2 F-001 — path traversal guard): rejects `""`, `.`, `..`, and any
|
|
56
|
+
* `/` or `\`; allows `[A-Za-z0-9._-]+` only. Throws with a clear message so
|
|
57
|
+
* callers interpolating a plan id into a path (archive files, SDD dirs)
|
|
58
|
+
* can never escape the intended parent directory.
|
|
59
|
+
*/
|
|
60
|
+
export declare function assertSafePathComponent(value: string, what: string): void;
|
|
61
|
+
/**
|
|
62
|
+
* Compose `{SDD_DIR}` = `{HARNESS_DIR}/sdd/<plan-id>/` (plan-conventions
|
|
63
|
+
* § 路径符号). The per-plan directory is created by the sdd workspace flow,
|
|
64
|
+
* not here. `planId` must be a single safe path component (traversal
|
|
65
|
+
* guard) — see `assertSafePathComponent`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveSddDir(harnessDir: string, planId: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Compose `{ITERATION_DIR}` = `{HARNESS_DIR}/iterations/` (plan-conventions
|
|
70
|
+
* § 路径符号).
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveIterationDir(harnessDir: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Initialize the harness directory under `root`: create `.mstar/` with
|
|
75
|
+
* `plans/`, `iterations/`, `knowledge/`, `specs/`, `sdd/` and write
|
|
76
|
+
* `status.json` from the empty template (plan-conventions § 初始化 Plan 目录).
|
|
77
|
+
* Idempotent: an existing non-empty `status.json` is never clobbered.
|
|
78
|
+
* Returns the absolute harness dir.
|
|
79
|
+
*/
|
|
80
|
+
export declare function scaffoldHarness(root: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* Harness kind for the gitignore fence — the canonical snippet is per
|
|
83
|
+
* harness layout (plan-conventions § Git 跟踪策略): `.mstar/` (default) and
|
|
84
|
+
* legacy `.agents/`.
|
|
85
|
+
*/
|
|
86
|
+
export type HarnessKind = "mstar" | "agents";
|
|
87
|
+
/**
|
|
88
|
+
* Emit the canonical `.gitignore` snippet for `kind` (plan-conventions
|
|
89
|
+
* § Git 跟踪策略): the process-artifact ignore set (`archived/`,
|
|
90
|
+
* `iterations/`, `plans/`, `sdd/`, `notes.json`, `status.json` under the
|
|
91
|
+
* harness dir) with the tracked/results note. When the kind is unknown
|
|
92
|
+
* (omitted), both snippets are emitted so either fence can be applied.
|
|
93
|
+
*/
|
|
94
|
+
export declare function emitGitignoreSnippet(kind?: HarnessKind): string;
|
|
95
|
+
/**
|
|
96
|
+
* Validate that `<root>/.gitignore` contains a complete canonical
|
|
97
|
+
* process-artifact ignore set (plan-conventions § Git 跟踪策略). Rule
|
|
98
|
+
* (chosen alignment): the gate passes when the repo's .gitignore holds ONE
|
|
99
|
+
* complete set for the DETECTED harness kind — `.mstar/` for a `.mstar`
|
|
100
|
+
* harness, `.agents/` for a legacy `.agents` harness; layouts without a
|
|
101
|
+
* canonical snippet (rung-3 `.plans`/`plans`, or no harness yet) accept
|
|
102
|
+
* either complete set. This is deliberately per-kind, unlike the CLI `init`
|
|
103
|
+
* fence which requires BOTH prefixes (flat dual-entry list — packages/cli
|
|
104
|
+
* src/adapters/shared-install.ts HARNESS_PROCESS_GITIGNORE); a repo fenced
|
|
105
|
+
* for one layout still passes here. Extra entries are fine; any missing
|
|
106
|
+
* entry of the required set is a violation. Non-blocking: returns a
|
|
107
|
+
* `ValidationResult` (v1 enforcement depth, roadmap §8.5).
|
|
108
|
+
*/
|
|
109
|
+
export declare function validateGitignore(root: string): ValidationResult;
|
|
110
|
+
/**
|
|
111
|
+
* Plan-writing path gate (plan-conventions § Plan-Writing Path Gate +
|
|
112
|
+
* harness-core 护栏): plans must live under `{PLAN_DIR}`; external default
|
|
113
|
+
* plan directories are rejected. When `harnessDir` is `null` (persistent
|
|
114
|
+
* plan tracking not enabled) any plan path is rejected with a fix pointing
|
|
115
|
+
* at `scaffoldHarness`. Non-blocking: returns a `ValidationResult`.
|
|
116
|
+
*/
|
|
117
|
+
export declare function assertPlanWritingPath(planPath: string, harnessDir: string | null): ValidationResult;
|
package/dist/roles.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { GateResult } from "./core.js";
|
|
2
|
+
/** One row of the role reference mapping (mstar-roles § Role Reference
|
|
3
|
+
* Mapping): `agentId` → skill-relative reference file. */
|
|
4
|
+
export type RoleMappingEntry = {
|
|
5
|
+
agentId: string;
|
|
6
|
+
reference: string;
|
|
7
|
+
};
|
|
8
|
+
/** The 13 role ids → `references/<role>.md` mapping, embedded as data
|
|
9
|
+
* (mstar-roles § Role Reference Mapping; shared families point at the
|
|
10
|
+
* shared reference files). */
|
|
11
|
+
export declare const ROLE_MAPPING: readonly RoleMappingEntry[];
|
|
12
|
+
/** A role family that MUST resolve to one shared reference file
|
|
13
|
+
* (mstar-roles § Maintenance Rules: "Keep shared-family roles
|
|
14
|
+
* (`fullstack-dev*`, `qc-specialist*`) on one shared reference file"). */
|
|
15
|
+
export type RoleFamily = {
|
|
16
|
+
family: string;
|
|
17
|
+
memberIds: readonly string[];
|
|
18
|
+
};
|
|
19
|
+
export declare const SHARED_FAMILIES: readonly RoleFamily[];
|
|
20
|
+
/** Dev-track parameter row (mstar-roles § Parameter Table (SSOT) — dev
|
|
21
|
+
* track): `primary` (backend-led) or `parallel_secondary` (second track). */
|
|
22
|
+
export type DevTrackParam = {
|
|
23
|
+
roleId: string;
|
|
24
|
+
track: "primary" | "parallel_secondary";
|
|
25
|
+
};
|
|
26
|
+
export declare const DEV_TRACK_PARAMS: readonly DevTrackParam[];
|
|
27
|
+
/** QC reviewer parameter row (mstar-roles § Parameter Table (SSOT) — QC
|
|
28
|
+
* reviewer): seat index, review focus, and the `qc<index>` report suffix
|
|
29
|
+
* that lands at `{SDD_DIR}/review/qc<index>.md`. */
|
|
30
|
+
export type QcReviewerParam = {
|
|
31
|
+
roleId: string;
|
|
32
|
+
reviewerIndex: number;
|
|
33
|
+
focus: string;
|
|
34
|
+
reportSuffix: string;
|
|
35
|
+
};
|
|
36
|
+
export declare const QC_REVIEWER_PARAMS: readonly QcReviewerParam[];
|
|
37
|
+
/** Override point for tests / future role-table extensions; every slot
|
|
38
|
+
* defaults to the embedded SSOT tables above. */
|
|
39
|
+
export type RoleMappingOptions = {
|
|
40
|
+
mapping?: readonly RoleMappingEntry[];
|
|
41
|
+
families?: readonly RoleFamily[];
|
|
42
|
+
devTrack?: readonly DevTrackParam[];
|
|
43
|
+
qcReviewers?: readonly QcReviewerParam[];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Validate the role mapping + parameter tables against the on-disk skill
|
|
47
|
+
* layout (mstar-roles § Role Reference Mapping / § Parameter Table (SSOT)
|
|
48
|
+
* / § Maintenance Rules):
|
|
49
|
+
* - every mapped agent id resolves to `references/<role>.md` under
|
|
50
|
+
* `rolesDir`;
|
|
51
|
+
* - shared families (`fullstack-dev*`, `qc-specialist*`) resolve to ONE
|
|
52
|
+
* shared reference file each;
|
|
53
|
+
* - every parameter row references a mapped role, exactly once;
|
|
54
|
+
* - dev track values are `primary` / `parallel_secondary`;
|
|
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>`.
|
|
58
|
+
*
|
|
59
|
+
* Violations:
|
|
60
|
+
* - `roles.mapping.reference.missing` — mapped reference file not on disk
|
|
61
|
+
* - `roles.mapping.family.member.missing` — family member absent from mapping
|
|
62
|
+
* - `roles.mapping.family.shared` — family members resolve to different files
|
|
63
|
+
* - `roles.param.role.missing` — parameter row names an unmapped role
|
|
64
|
+
* - `roles.param.role.duplicate` — role appears in two parameter rows
|
|
65
|
+
* - `roles.param.track` — dev track value not primary/parallel_secondary
|
|
66
|
+
* - `roles.param.qc.index.set` — reviewer_index set ≠ {1, 2, 3} (high)
|
|
67
|
+
* - `roles.param.qc.focus.missing` — QC seat without a focus
|
|
68
|
+
* - `roles.param.qc.suffix` — report_suffix ≠ qc<reviewer_index>
|
|
69
|
+
*/
|
|
70
|
+
export declare function validateRoleMapping(rolesDir: string, options?: RoleMappingOptions): GateResult;
|
|
71
|
+
/**
|
|
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).
|
|
76
|
+
*
|
|
77
|
+
* 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).
|
|
82
|
+
*
|
|
83
|
+
* Violations:
|
|
84
|
+
* - `roles.loadorder.section.missing` — no Load Order / First action section
|
|
85
|
+
* - `roles.loadorder.core.missing` — section exists without the core mention
|
|
86
|
+
*/
|
|
87
|
+
export declare function lintLoadOrder(skillTexts: Record<string, string>): GateResult;
|
package/dist/sdd.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error carrying the ported script exit code so the CLI can map validation
|
|
3
|
+
* failures to identical non-zero exits.
|
|
4
|
+
*/
|
|
5
|
+
export declare class SddScriptError extends Error {
|
|
6
|
+
readonly exitCode: number;
|
|
7
|
+
constructor(message: string, exitCode: number);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Options for `sddWorkspace` — `mstar sdd workspace PLAN_ID [CONTROL_ROOT]`
|
|
11
|
+
* usage plus the harness-root override (plan finding 2026-08-08).
|
|
12
|
+
*/
|
|
13
|
+
export type SddWorkspaceOptions = {
|
|
14
|
+
/** Control worktree repo root — CLI 2nd arg / `MSTAR_CONTROL_ROOT`. */
|
|
15
|
+
controlRoot?: string;
|
|
16
|
+
/** Explicit harness root — `MSTAR_HARNESS_DIR` / `--harness-dir`. */
|
|
17
|
+
harnessDir?: string;
|
|
18
|
+
/** Working directory for git probes; default `process.cwd()`. */
|
|
19
|
+
cwd?: string;
|
|
20
|
+
};
|
|
21
|
+
/** Options for `taskBrief` (mirrors `$SDD_DIR` for the default out path). */
|
|
22
|
+
export type TaskBriefOptions = {
|
|
23
|
+
sddDir?: string;
|
|
24
|
+
};
|
|
25
|
+
/** Options for `reviewPackage` (mirrors `$SDD_DIR` + git probe cwd). */
|
|
26
|
+
export type ReviewPackageOptions = {
|
|
27
|
+
sddDir?: string;
|
|
28
|
+
cwd?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Resolve and ensure `{SDD_DIR}` = `{HARNESS_DIR}/sdd/<plan-id>/` (prints
|
|
32
|
+
* the absolute path). Resolution order:
|
|
33
|
+
*
|
|
34
|
+
* 1. fail-closed FIRST: a linked worktree without a control root never
|
|
35
|
+
* resolves or creates any SDD tree under the feature checkout (refuses a
|
|
36
|
+
* second SDD tree; no override or probe may bypass this guard);
|
|
37
|
+
* 2. explicit harness-root override (`opts.harnessDir` / `MSTAR_HARNESS_DIR`)
|
|
38
|
+
* — plan finding 2026-08-08: covers `.harness`-rooted repos the
|
|
39
|
+
* status.json probe misses; resolved relative to the established root;
|
|
40
|
+
* 3. `status.json` probe at root (`.mstar` → `.agents`);
|
|
41
|
+
* 4. fallback: existing `.mstar`/`.agents` dir, else `.mstar`.
|
|
42
|
+
*
|
|
43
|
+
* `controlRoot` (CLI 2nd arg / `MSTAR_CONTROL_ROOT`) pins `root` to the
|
|
44
|
+
* control worktree instead of the cwd's git top-level.
|
|
45
|
+
*/
|
|
46
|
+
export declare function sddWorkspace(planId: string, opts?: SddWorkspaceOptions): string;
|
|
47
|
+
/**
|
|
48
|
+
* Extract the `## Task N` section of a plan into a file (default
|
|
49
|
+
* `{SDD_DIR}/task-N-brief.md`). Line state machine: ``` fences toggle
|
|
50
|
+
* `infence`; headings inside fences are ignored; printing starts at the
|
|
51
|
+
* heading for `taskN` and continues until the NEXT `## Task` heading (or
|
|
52
|
+
* EOF for the last task) — a later Task heading resets the section. A
|
|
53
|
+
* missing task writes an empty file then fails with exit-3
|
|
54
|
+
* (`SddScriptError.exitCode === 3`).
|
|
55
|
+
*/
|
|
56
|
+
export declare function taskBrief(planFile: string, taskN: number, outFile?: string, opts?: TaskBriefOptions): string;
|
|
57
|
+
/**
|
|
58
|
+
* Write commit list, stat summary and `git diff -U10` for `BASE..HEAD`
|
|
59
|
+
* into a file (default `{SDD_DIR}/review-<short base>..<short head>.diff`).
|
|
60
|
+
* Both refs are validated with `git rev-parse --verify --quiet` (any ref
|
|
61
|
+
* the original accepted is accepted here; the SHA-only guard is
|
|
62
|
+
* `assertBaseSha`).
|
|
63
|
+
*/
|
|
64
|
+
export declare function reviewPackage(base: string, head: string, outFile?: string, opts?: ReviewPackageOptions): string;
|
|
65
|
+
/**
|
|
66
|
+
* BASE_SHA guard (mstar-sdd SKILL.md red flags: never use `HEAD~1` as the
|
|
67
|
+
* review BASE — multi-commit tasks truncate). Accepts only a full or prefix
|
|
68
|
+
* commit SHA that exists in the repo; throws `SddScriptError` (exit 2)
|
|
69
|
+
* otherwise.
|
|
70
|
+
*/
|
|
71
|
+
export declare function assertBaseSha(ref: string, opts?: {
|
|
72
|
+
cwd?: string;
|
|
73
|
+
}): void;
|
|
74
|
+
/**
|
|
75
|
+
* True when `{sddDir}/task-N-report.md` exists and is non-empty
|
|
76
|
+
* (file-handoffs.md: the implementer writes a full report to
|
|
77
|
+
* `task-N-report.md`; an empty file carries no evidence).
|
|
78
|
+
*/
|
|
79
|
+
export declare function taskReportExists(sddDir: string, taskN: number): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Read the progress ledger (mstar-sdd SKILL.md § Progress ledger) as
|
|
82
|
+
* non-empty trimmed lines; missing `progress.md` reads as `[]`. Tasks
|
|
83
|
+
* marked `Task N: complete` are DONE and must not be re-dispatched.
|
|
84
|
+
*/
|
|
85
|
+
export declare function readProgressLedger(sddDir: string): string[];
|
|
86
|
+
/**
|
|
87
|
+
* Sticky implementer session ledger — `{SDD_DIR}/implementer-session.json`
|
|
88
|
+
* (sticky-implementer-session.md § Session ledger).
|
|
89
|
+
*/
|
|
90
|
+
export type ImplementerSessionLedger = {
|
|
91
|
+
plan_id: string;
|
|
92
|
+
execute_as: string;
|
|
93
|
+
session_mode: "sticky" | "fresh";
|
|
94
|
+
host: string;
|
|
95
|
+
/** Agent id from the first Task return — required for resume. */
|
|
96
|
+
host_agent_id?: string;
|
|
97
|
+
working_branch: string;
|
|
98
|
+
started_task: number;
|
|
99
|
+
last_task: number;
|
|
100
|
+
started_at: string;
|
|
101
|
+
};
|
|
102
|
+
/** Input to `implementerSessionStickyRules`. */
|
|
103
|
+
export type StickyRulesInput = {
|
|
104
|
+
session: ImplementerSessionLedger;
|
|
105
|
+
/** Task about to be dispatched. */
|
|
106
|
+
nextTask: number;
|
|
107
|
+
/** Tasks covered by this dispatch (micro-batch); default 1. */
|
|
108
|
+
microBatchTasks?: number;
|
|
109
|
+
};
|
|
110
|
+
/** Verdict of the sticky resume rules. */
|
|
111
|
+
export type StickyRulesResult = {
|
|
112
|
+
resume: boolean;
|
|
113
|
+
reason: string;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Sticky resume rules (sticky-implementer-session.md + SKILL.md red flag
|
|
117
|
+
* "Resume implementer without host_agent_id"): a sticky session may only
|
|
118
|
+
* resume when `session_mode` is `sticky`, `host_agent_id` is present,
|
|
119
|
+
* `nextTask` is not already completed through `last_task`, and the
|
|
120
|
+
* micro-batch size is ≤ 3 (max without user override). Reviewers never
|
|
121
|
+
* resume — that rule lives in the PM flow, not the session ledger.
|
|
122
|
+
*/
|
|
123
|
+
export declare function implementerSessionStickyRules(input: StickyRulesInput): StickyRulesResult;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine skill-authoring module — frontmatter lint, 5-question body lint,
|
|
3
|
+
* and skill-relative asset-path resolution (thin; roadmap §8.2
|
|
4
|
+
* `skill-authoring` row, §4.5).
|
|
5
|
+
*
|
|
6
|
+
* Source skills (semantic SSOT — this module implements their deterministic
|
|
7
|
+
* rules, it never redefines them; roadmap §8.5 C2):
|
|
8
|
+
* - `mstar-skill-authoring` SKILL.md § Frontmatter Contract — `name` stable
|
|
9
|
+
* lowercase-hyphen; `description` is the trigger contract (not a workflow
|
|
10
|
+
* summary), third person.
|
|
11
|
+
* - `mstar-skill-authoring` SKILL.md § Body 必须回答的 5 问 + § 默认 Body
|
|
12
|
+
* 结构 — a SKILL.md body answers five questions via key sections (Load
|
|
13
|
+
* Order, Workflow, Decision Rules, Evidence, References).
|
|
14
|
+
* - `mstar-skill-authoring` SKILL.md § Skill-relative script and asset
|
|
15
|
+
* paths ("skill `my-skill` → scripts/do-thing") + `mstar-host` SKILL.md
|
|
16
|
+
* § Resolve loaded skill root (per-host resolution).
|
|
17
|
+
*
|
|
18
|
+
* The SkillsBench six principles and trigger-contract reasoning stay prompt.
|
|
19
|
+
*/
|
|
20
|
+
import type { GateResult } from "./core.js";
|
|
21
|
+
import { type HostId } from "./host.js";
|
|
22
|
+
/**
|
|
23
|
+
* Lint a skill file's frontmatter (mstar-skill-authoring § Frontmatter
|
|
24
|
+
* Contract): `name` lowercase-hyphen, `description` present / third-person /
|
|
25
|
+
* not a workflow summary. Re-exports `lint.lintSkillFrontmatter` — the
|
|
26
|
+
* single parser/heuristics implementation (one source of truth; the CLI
|
|
27
|
+
* `mstar skill lint` calls this alias). Violations keep the `lint.frontmatter.*`
|
|
28
|
+
* codes.
|
|
29
|
+
*/
|
|
30
|
+
export { lintSkillFrontmatter as lintFrontmatter } from "./lint.js";
|
|
31
|
+
/** One of the five body questions and the canonical section that answers it
|
|
32
|
+
* (mstar-skill-authoring § Body 必须回答的 5 问 / § 默认 Body 结构). */
|
|
33
|
+
export type FiveQuestionSection = {
|
|
34
|
+
key: string;
|
|
35
|
+
label: string;
|
|
36
|
+
question: string;
|
|
37
|
+
};
|
|
38
|
+
export declare const FIVE_QUESTION_SECTIONS: readonly FiveQuestionSection[];
|
|
39
|
+
/**
|
|
40
|
+
* Lint a SKILL.md body for the 5-question contract (mstar-skill-authoring
|
|
41
|
+
* § Body 必须回答的 5 问). Heuristic: each of the five questions must be
|
|
42
|
+
* answered by the presence of its canonical section heading (case-
|
|
43
|
+
* insensitive substring match on heading text, any heading level — so
|
|
44
|
+
* "## Load Order (Required)" and "### Workflow — main path" both match).
|
|
45
|
+
* Content judgment (whether the answer is actually narrow / procedural)
|
|
46
|
+
* stays prompt. Advisory: violations are `low` severity (v1 non-blocking).
|
|
47
|
+
*
|
|
48
|
+
* Violations: `skill-authoring.five-question.<key>` for each uncovered
|
|
49
|
+
* question.
|
|
50
|
+
*/
|
|
51
|
+
export declare function lintFiveQuestion(bodyText: string): GateResult;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a skill-relative asset path (mstar-skill-authoring § Skill-
|
|
54
|
+
* relative script and asset paths: name assets as skill `<name>` →
|
|
55
|
+
* `scripts/…` / `references/…`; never a literal `skills/<name>/…` path from
|
|
56
|
+
* a consumer cwd) into the host-specific resolution instruction
|
|
57
|
+
* (mstar-host § Resolve loaded skill root). No filesystem access — this is
|
|
58
|
+
* an instruction string an agent reads to find the asset.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveAssetPath(skillName: string, relPath: string, host: HostId): string;
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { type GateResult } from "./core.js";
|
|
2
|
+
import { type EnforcementFlag } from "./dispatch.js";
|
|
3
|
+
/**
|
|
4
|
+
* Loose shape of a parsed status.json document. All fields are `unknown`
|
|
5
|
+
* because documents come from JSON at runtime; validators narrow them.
|
|
6
|
+
*/
|
|
7
|
+
export type StatusDoc = {
|
|
8
|
+
version?: unknown;
|
|
9
|
+
updated_at?: unknown;
|
|
10
|
+
plans?: unknown;
|
|
11
|
+
residual_findings?: unknown;
|
|
12
|
+
metadata?: unknown;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
};
|
|
15
|
+
/** Residual entry as parsed from status.json (loose — validated by `validateResidual`). */
|
|
16
|
+
export type ResidualEntry = {
|
|
17
|
+
id?: unknown;
|
|
18
|
+
title?: unknown;
|
|
19
|
+
severity?: unknown;
|
|
20
|
+
source?: unknown;
|
|
21
|
+
scope?: unknown;
|
|
22
|
+
decision?: unknown;
|
|
23
|
+
owner?: unknown;
|
|
24
|
+
target?: unknown;
|
|
25
|
+
tracking?: unknown;
|
|
26
|
+
detail_doc?: unknown;
|
|
27
|
+
lifecycle?: unknown;
|
|
28
|
+
closed_at?: unknown;
|
|
29
|
+
closure_note?: unknown;
|
|
30
|
+
closure_evidence?: unknown;
|
|
31
|
+
superseded_by?: unknown;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
};
|
|
34
|
+
/** Plan row as parsed from status.json (loose — validated by `validatePlanRow`). */
|
|
35
|
+
export type PlanRow = {
|
|
36
|
+
id?: unknown;
|
|
37
|
+
plan_id?: unknown;
|
|
38
|
+
title?: unknown;
|
|
39
|
+
file?: unknown;
|
|
40
|
+
status?: unknown;
|
|
41
|
+
metadata?: unknown;
|
|
42
|
+
execution_lease?: unknown;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
};
|
|
45
|
+
/** Findings cleanup policy mirror of Assignment `Findings cleanup`. */
|
|
46
|
+
export type FindingsCleanupMode = "zero-residual" | "allow-residual";
|
|
47
|
+
/** Computed rollup aggregates (jq semantics). */
|
|
48
|
+
export type TechDebtSummary = {
|
|
49
|
+
total_open: number;
|
|
50
|
+
by_severity: Record<string, number>;
|
|
51
|
+
by_target: Record<string, number>;
|
|
52
|
+
by_plan: Record<string, number>;
|
|
53
|
+
};
|
|
54
|
+
export type TechDebtCheck = {
|
|
55
|
+
field: "total_open" | "by_severity" | "by_target" | "by_plan";
|
|
56
|
+
status: "PASS" | "DRIFT";
|
|
57
|
+
};
|
|
58
|
+
/** Result of the rollup + drift check vs stored `metadata.tech_debt_summary`. */
|
|
59
|
+
export type TechDebtRollup = {
|
|
60
|
+
computed: TechDebtSummary;
|
|
61
|
+
stored: Record<string, unknown> | null;
|
|
62
|
+
checks: TechDebtCheck[];
|
|
63
|
+
overall: "PASS" | "DRIFT";
|
|
64
|
+
};
|
|
65
|
+
export type ArchiveResult = {
|
|
66
|
+
planId: string;
|
|
67
|
+
archived: number;
|
|
68
|
+
archivePath: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Normalize a residual `severity` value for reading/rolling up
|
|
72
|
+
* (status-and-residuals.md § severity 5 + rollup `norm_sev`):
|
|
73
|
+
* legacy `"warning"` → `"low"`; `null`/`""` → `"medium"`; anything else passes
|
|
74
|
+
* through unchanged (unknown values match no enum bucket, same as jq).
|
|
75
|
+
*/
|
|
76
|
+
export declare function normalizeSeverity(value: unknown): unknown;
|
|
77
|
+
/**
|
|
78
|
+
* jq semantics: an entry is open when `.lifecycle // "open"` equals `"open"`
|
|
79
|
+
* (rollup `is_open`; status-and-residuals.md § lifecycle).
|
|
80
|
+
* The jq alternative operator `//` yields the default for `false` AND
|
|
81
|
+
* `null` (not just null) — `lifecycle: false` therefore counts as open.
|
|
82
|
+
* Exported for consumers that need the shared open semantics (e.g. the
|
|
83
|
+
* iteration phase-gate entry check) instead of a local re-implementation.
|
|
84
|
+
*/
|
|
85
|
+
export declare function isOpenResidual(entry: Record<string, unknown>): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Validate one `plans[]` row (status-and-residuals.md § Basic structure +
|
|
88
|
+
* § Compatibility: read accepts `id` or `plan_id`; write one canonical key).
|
|
89
|
+
* Required: `id` (or legacy `plan_id`), `title`, `file`, `status` (one of
|
|
90
|
+
* Todo|InProgress|InReview|Blocked|Done); `metadata` optional but must be an
|
|
91
|
+
* object when present. `execution_lease` is type-checked here; the full lease
|
|
92
|
+
* state machine lives in the lease module (Task 5).
|
|
93
|
+
*/
|
|
94
|
+
export declare function validatePlanRow(row: unknown): GateResult;
|
|
95
|
+
/**
|
|
96
|
+
* Validate one residual entry (status-and-residuals.md § Basic structure):
|
|
97
|
+
* required `id`, `title`, `severity`, `source`, `scope`, `decision`, `owner`,
|
|
98
|
+
* `target`, `tracking`; `severity` from the enum (legacy `"warning"` is read
|
|
99
|
+
* but forbidden on new entries — flagged with fix); `decision` from
|
|
100
|
+
* defer|accept|risk-accepted; `lifecycle` optional from the close enum.
|
|
101
|
+
*/
|
|
102
|
+
export declare function validateResidual(entry: unknown): GateResult;
|
|
103
|
+
/**
|
|
104
|
+
* Validate a status.json document (schema: status-and-residuals.md
|
|
105
|
+
* § Basic structure + § General constraints). Accepts a parsed document or a
|
|
106
|
+
* file path (malformed JSON yields a `status.invalid-json` violation, never a
|
|
107
|
+
* throw). Required top-level fields: `version`, `updated_at`, `plans[]`,
|
|
108
|
+
* root-only `residual_findings`, `metadata`. Root-only canonical: any
|
|
109
|
+
* `metadata.residual_findings` key is flagged as dual-write.
|
|
110
|
+
*/
|
|
111
|
+
export declare function validateStatus(docOrPath: StatusDoc | string): GateResult;
|
|
112
|
+
/**
|
|
113
|
+
* Archive the open residuals of a plan (status-and-residuals.md
|
|
114
|
+
* § Residual findings lifecycle): append every entry of
|
|
115
|
+
* `residual_findings[<plan-id>]` to `{HARNESS_DIR}/archived/residuals/
|
|
116
|
+
* <plan-id>.json` (stamped `archived_at`), delete the key from the open list,
|
|
117
|
+
* and bump root `updated_at`. No-op (archived 0) when the plan has no open
|
|
118
|
+
* residuals. `harnessDir` defaults to the resolved `{HARNESS_DIR}` from cwd.
|
|
119
|
+
*
|
|
120
|
+
* `planId` is validated as a single safe path component before it is used to
|
|
121
|
+
* build the archive path (path traversal guard — see
|
|
122
|
+
* `assertSafePathComponent`). The status.json read-modify-write runs under
|
|
123
|
+
* `withStatusWriteLock` so concurrent coordination writers serialize.
|
|
124
|
+
*
|
|
125
|
+
* simplify: archive append + status.json update are two separate writes —
|
|
126
|
+
* a crash between them leaves entries both archived and open; re-running is
|
|
127
|
+
* safe because appends dedup on `entries[].id` (F-10). Not transactional by
|
|
128
|
+
* design (v1); the write lock from F-004 keeps concurrent writers safe.
|
|
129
|
+
*/
|
|
130
|
+
export declare function archiveResiduals(planId: string, harnessDir?: string): Promise<ArchiveResult>;
|
|
131
|
+
/**
|
|
132
|
+
* Findings cleanup gate (status-and-residuals.md § Findings cleanup modes).
|
|
133
|
+
* `zero-residual`: only true blocker-defers (`decision: defer` + non-empty
|
|
134
|
+
* `target`) may stay open — fixable findings, `nit`s, and waived/
|
|
135
|
+
* risk-accepted entries are violations. `allow-residual` (default): open
|
|
136
|
+
* residuals are fine unless an unresolved Critical remains. Mode resolution:
|
|
137
|
+
* explicit `opts.mode` → `plans[].metadata.findings_cleanup` → `allow-residual`.
|
|
138
|
+
*/
|
|
139
|
+
export declare function findingsCleanupGate(doc: StatusDoc, planId: string, opts?: {
|
|
140
|
+
mode?: FindingsCleanupMode;
|
|
141
|
+
}): GateResult;
|
|
142
|
+
/**
|
|
143
|
+
* Resolve the repo-level hard-enforcement flag from the iteration compass
|
|
144
|
+
* (roadmap §8.5 C4/D2): `{ITERATION_DIR}/<id>/delivery-compass.md` files are
|
|
145
|
+
* scanned; only compasses still steering the repo count — frontmatter
|
|
146
|
+
* `status: active` or `status: locked` — and the FIRST such compass whose
|
|
147
|
+
* frontmatter declares `enforcement: hard` hardens the gate in this repo.
|
|
148
|
+
* A COMPLETED (or status-less/archived) iteration's compass NEVER hardens:
|
|
149
|
+
* D2 rollback = unset the flag in the ACTIVE compass, and that must work
|
|
150
|
+
* while older completed compasses still declare hard (qc1 F-001 / qc2 F-002).
|
|
151
|
+
* A counting compass declaring a non-hard value, or no compass at all,
|
|
152
|
+
* leaves the flag unset (`source: none`) — hard gates are never the default
|
|
153
|
+
* and the flag is inert when the engine is absent. Frontmatter is
|
|
154
|
+
* `---`-fenced; hard declarations in the compass BODY do not count (the
|
|
155
|
+
* frontmatter is the schema surface — see iteration.compassSchema).
|
|
156
|
+
*/
|
|
157
|
+
export declare function resolveCompassEnforcement(harnessDir: string): EnforcementFlag;
|
|
158
|
+
/**
|
|
159
|
+
* Compute the `metadata.tech_debt_summary` rollup (status-and-residuals.md
|
|
160
|
+
* § `metadata.tech_debt_summary`): `total_open` / `by_severity` /
|
|
161
|
+
* `by_target` / `by_plan` over open entries of root `residual_findings`
|
|
162
|
+
* merged with the legacy `metadata.residual_findings` read path (canonical
|
|
163
|
+
* keys win; legacy `"warning"` → `low`, `null`/`""` → `medium`; closed
|
|
164
|
+
* entries skipped; missing `target` groups under `"unspecified"`), then
|
|
165
|
+
* compare field-by-field against stored `metadata.tech_debt_summary`.
|
|
166
|
+
* Accepts a parsed document or a file path. Does not write status.json.
|
|
167
|
+
*/
|
|
168
|
+
export declare function techDebtRollup(docOrPath: StatusDoc | string): TechDebtRollup;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { GateResult } from "./core.js";
|
|
2
|
+
/** One L2 parallel implement track (mstar-branch-worktree L2 table). */
|
|
3
|
+
export type WorktreeTrack = {
|
|
4
|
+
/** Absolute worktree checkout path for the track. */
|
|
5
|
+
worktreePath: string;
|
|
6
|
+
/** PM-approved Working branch checked out in that worktree. */
|
|
7
|
+
workingBranch: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* L1 pre-dispatch checklist input — mirrors the status.json L1 fields
|
|
11
|
+
* (`metadata.control_worktree_path` + `plans[].execution_lease`).
|
|
12
|
+
*/
|
|
13
|
+
export type L1PreDispatchInput = {
|
|
14
|
+
/** `metadata.control_worktree_path` — harness coordination SSOT checkout. */
|
|
15
|
+
controlWorktreePath: string;
|
|
16
|
+
/** `execution_lease.worktree_path` — the plan's feature worktree. */
|
|
17
|
+
leaseWorktreePath: string;
|
|
18
|
+
/** `execution_lease.working_branch` — the plan's Working branch. */
|
|
19
|
+
leaseWorkingBranch: string;
|
|
20
|
+
/** Plan id (`status.json.plans[].id` / `{SDD_DIR}` segment) — message context. */
|
|
21
|
+
planId: string;
|
|
22
|
+
};
|
|
23
|
+
/** L2 pre-dispatch checklist input — the plan's parallel writable tracks. */
|
|
24
|
+
export type L2PreDispatchInput = {
|
|
25
|
+
tracks: readonly WorktreeTrack[];
|
|
26
|
+
};
|
|
27
|
+
/** Git branch probe options — keep checks pure by precomputing probe inputs. */
|
|
28
|
+
export type BranchProbeOptions = {
|
|
29
|
+
/** git executable to invoke (default `git`). */
|
|
30
|
+
gitPath?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Precomputed branch lookup keyed by absolute worktree path; return
|
|
33
|
+
* `undefined` to fall back to a real `git -C <path> branch --show-current`
|
|
34
|
+
* probe. Lets callers (tests, host hooks) inject probe inputs without a
|
|
35
|
+
* subprocess.
|
|
36
|
+
*/
|
|
37
|
+
branchOf?: (worktreePath: string) => string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Git probe timeout in ms (default 10s; `MSTAR_GIT_PROBE_TIMEOUT_MS` env
|
|
40
|
+
* overrides; per-call value wins). On timeout the probe fails closed into
|
|
41
|
+
* `branch-probe-failed` — never hangs, never guesses a branch (qc3 F-4).
|
|
42
|
+
*/
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* QC/QA alignment fields — `plan_id` + `Review range`/`Diff basis` must be
|
|
47
|
+
* byte-identical across the QC tri + QA assignments (逐字相同).
|
|
48
|
+
*/
|
|
49
|
+
export type QcAlignmentAssignment = {
|
|
50
|
+
planId: string;
|
|
51
|
+
reviewRange: string;
|
|
52
|
+
diffBasis: string;
|
|
53
|
+
};
|
|
54
|
+
/** `singleReviewSnapshot` input — alignment fields plus a precomputed review HEAD. */
|
|
55
|
+
export type QcSnapshotAssignment = QcAlignmentAssignment & {
|
|
56
|
+
/** Precomputed review HEAD (full SHA preferred) for that assignment. */
|
|
57
|
+
head?: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* L1 cross-plan pre-dispatch checklist (mstar-branch-worktree L1 table +
|
|
61
|
+
* Harness path SSOT hard rules): control path recorded, feature worktree
|
|
62
|
+
* exists, lease worktree ≠ control path, and the branch checked out at the
|
|
63
|
+
* feature worktree matches `execution_lease.working_branch`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function l1PreDispatchCheck(input: L1PreDispatchInput, opts?: BranchProbeOptions): GateResult;
|
|
66
|
+
/**
|
|
67
|
+
* L2 within-plan pre-dispatch checklist (parallel-writable-pre-dispatch.md):
|
|
68
|
+
* every parallel writable track's `worktreePath` must be absolute and
|
|
69
|
+
* distinct (one Worktree per track — N parallel invokes ≠ isolation), the
|
|
70
|
+
* worktree dir must exist, and `git -C <path> branch --show-current` must
|
|
71
|
+
* match its Working branch — before the first concurrent writable dispatch.
|
|
72
|
+
* Fewer than one track is itself a violation (the checklist needs something
|
|
73
|
+
* to verify).
|
|
74
|
+
*/
|
|
75
|
+
export declare function l2PreDispatchCheck(input: L2PreDispatchInput, opts?: BranchProbeOptions): GateResult;
|
|
76
|
+
/**
|
|
77
|
+
* L1 hard rule (Harness path SSOT): `execution_lease.worktree_path` MUST
|
|
78
|
+
* differ from `metadata.control_worktree_path`. String equality on the two
|
|
79
|
+
* paths — canonical absolute paths are the caller's contract (the lease
|
|
80
|
+
* validator already requires `worktree_path` to be absolute).
|
|
81
|
+
*/
|
|
82
|
+
export declare function assertControlVsFeaturePath(controlWorktreePath: string, featureWorktreePath: string): GateResult;
|
|
83
|
+
/**
|
|
84
|
+
* Assert the branch checked out at `worktreePath` matches `expectedBranch`
|
|
85
|
+
* (the Assignment Working branch). Probe = `git -C <path> branch
|
|
86
|
+
* --show-current`; precompute via `opts.branchOf` for purity. Fail-closed on
|
|
87
|
+
* probe errors and detached HEAD.
|
|
88
|
+
*/
|
|
89
|
+
export declare function assertBranchAlignment(worktreePath: string, expectedBranch: string, opts?: BranchProbeOptions): GateResult;
|
|
90
|
+
/**
|
|
91
|
+
* QC/QA 对齐字段契约: `plan_id` + `Review range`/`Diff basis` must be
|
|
92
|
+
* byte-identical (逐字相同 — no trimming, no normalization) across every
|
|
93
|
+
* assignment in the set, so the QC tri + QA review the same plan/feature and
|
|
94
|
+
* the same diff range. One violation per field that differs.
|
|
95
|
+
*/
|
|
96
|
+
export declare function assertQcAlignment(assignments: readonly QcAlignmentAssignment[]): GateResult;
|
|
97
|
+
/**
|
|
98
|
+
* Single review snapshot precondition (派 QC 前置条件): all reviewable
|
|
99
|
+
* commits must sit on ONE Working branch HEAD before the QC tri + QA are
|
|
100
|
+
* dispatched. `head` is precomputed per assignment (full SHA preferred);
|
|
101
|
+
* different heads → violation; a missing head cannot confirm the snapshot →
|
|
102
|
+
* violation (fail-closed).
|
|
103
|
+
*/
|
|
104
|
+
export declare function singleReviewSnapshot(assignments: readonly QcSnapshotAssignment[]): GateResult;
|