@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/dist/host.d.ts ADDED
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Engine host module — host auto-detection from tool shapes, skill-root
3
+ * resolution, and the type-only HostAdapter contract (thin; roadmap §8.2
4
+ * `host` row, §4.2/§8.4).
5
+ *
6
+ * Source skills (semantic SSOT — this module ports their deterministic
7
+ * rules verbatim, it never redefines them; roadmap §8.5 C2):
8
+ * - `mstar-host` SKILL.md § Detect active host — the ordered table
9
+ * cursor → opencode → omp → kimi → zcode → codex ("Order matters").
10
+ * - `mstar-host` SKILL.md § Resolve loaded skill root — per-host skill-root
11
+ * resolution.
12
+ * - `.harness/references/skill-programmatic-roadmap.md` §8.4 — the
13
+ * `HostAdapter` shared contract (all hooks optional; no concrete adapters
14
+ * in the engine; pi/dsh deferred).
15
+ *
16
+ * UX judgment (ambiguous-host fallback reasoning, plan-mode bridges) stays
17
+ * prompt — this module only turns tool shapes into a host id.
18
+ */
19
+ import type { GateResult, ValidationResult } from "./core.js";
20
+ import type { AssignmentFields } from "./dispatch.js";
21
+ import type { IntegrationMergeLease } from "./lease.js";
22
+ /** All hosts the engine knows about (roadmap §8.4 host union). `pi` and
23
+ * `dsh` have no plugin API in v1 — they appear in the union (and in
24
+ * `HostAdapter.host`) but are never detected and get no adapters. */
25
+ export type HostId = "opencode" | "omp" | "pi" | "dsh" | "cursor" | "codex" | "kimi" | "zcode";
26
+ /** Result of `detectHost`: one of the six known hosts or `ambiguous`
27
+ * (prompt judgment then applies per mstar-host). */
28
+ export type DetectResult = "opencode" | "omp" | "cursor" | "codex" | "kimi" | "zcode" | "ambiguous";
29
+ /** Tool-shape signal tokens accepted by `detectHost`, derived from the
30
+ * mstar-host detection table. Plan-mode extras (CreatePlan/SwitchMode) and
31
+ * Browser-plugin tools are documented in the table but are not part of the
32
+ * v1 signal enum. */
33
+ export type ToolSignal = "subagent_type" | "question" | "task_subagent" | "task_agent_batch" | "ask" | "hub" | "Agent" | "AgentSwarm" | "AskUserQuestion" | "EnterPlanMode" | "TodoWrite" | "plan_slash" | "goal" | "functions.*" | "tool_search";
34
+ /**
35
+ * Detect the active host from session tool shapes, per the ordered
36
+ * mstar-host table (ported verbatim):
37
+ *
38
+ * | Signal | Host |
39
+ * |--------|------|
40
+ * | `subagent_type` (Task param; plan mode + CreatePlan/SwitchMode) | cursor |
41
+ * | `question`, or `task_subagent` (task tool, singular subagent, no batch) | opencode |
42
+ * | `task_agent_batch` (task tool, agent/tasks[] batch), `ask`, `hub` | omp |
43
+ * | `Agent`/`AskUserQuestion`/`EnterPlanMode` + `AgentSwarm` (Kimi-only) | kimi |
44
+ * | `Agent`/`AskUserQuestion`/`EnterPlanMode`/`TodoWrite`, no `AgentSwarm` | zcode |
45
+ * | `/plan`, `/goal`; Goal tools; `functions.*` namespaces; `tool_search` | codex |
46
+ *
47
+ * Order matters: cursor → opencode → omp → kimi → zcode → codex — the
48
+ * sharpest Task-based split is `subagent_type` (Cursor) vs `subagent`
49
+ * (OpenCode) vs `agent`/`tasks[]` (omp). Still ambiguous → `"ambiguous"`
50
+ * (prompt judgment stays in the skill).
51
+ */
52
+ export declare function detectHost(signals: readonly ToolSignal[]): DetectResult;
53
+ /** Skill name + optional skill-relative path for skill-root resolution. */
54
+ export type SkillRootPaths = {
55
+ skill: string;
56
+ rel?: string;
57
+ };
58
+ /**
59
+ * Resolve the loaded skill root for a host (mstar-host § Resolve loaded
60
+ * skill root). Returns the canonical resolution string for the host —
61
+ * resolve the loaded skill directory first, never `skills/<name>/…` from a
62
+ * consumer app cwd (that layout exists in the harness source / plugin
63
+ * package only).
64
+ *
65
+ * | Host | Resolution |
66
+ * |------|------------|
67
+ * | omp | `skill://<name>[/<rel>]` (filesystem fallback: plugin package root `skills/<name>/` after install/link) |
68
+ * | cursor | `~/.cursor/plugins/local/morning-star-harness/skills/<name>[/<rel>]` (global plugin fallback; prefer skill name via plugin skills) |
69
+ * | codex | `skills/<name>[/<rel>]` (plugin-mounted; project command skills under `.agents/skills/<name>/`) |
70
+ * | opencode | `harness-skills/<name>[/<rel>]` (package-internal via `@mstar-harness/opencode` — never `process.cwd()/skills/`) |
71
+ * | kimi / zcode | `./skills/<name>[/<rel>]` (plugin mount from the installed plugin root) |
72
+ * | pi / dsh | deferred — no plugin API in v1 (roadmap §8.4) |
73
+ */
74
+ export declare function resolveSkillRoot(host: HostId, paths: SkillRootPaths): string;
75
+ /**
76
+ * Shared host-adapter contract (roadmap §8.4) — every host plugin implements
77
+ * this; the engine never imports host-specific SDKs. All lifecycle hooks are
78
+ * optional so a host degrades gracefully when a slot is absent (standalone
79
+ * rule: skill text remains authoritative; the engine only returns results
80
+ * the caller chooses to honor). `log` is required — adapters must be able to
81
+ * report. No concrete adapters ship in the engine: opencode binds via its
82
+ * own plugin code, omp via the command layer; pi/dsh adapters are deferred
83
+ * until their plugin APIs land.
84
+ */
85
+ export interface HostAdapter {
86
+ host: "opencode" | "omp" | "pi" | "dsh" | "cursor" | "codex" | "kimi" | "zcode";
87
+ /** Called before a status.json write; return a validation result the host
88
+ * surfaces (non-blocking warn in v1, opt-in block in v2). */
89
+ beforeStatusWrite?: (path: string, doc: unknown) => Promise<ValidationResult>;
90
+ /** Called before a subagent dispatch; return the Assignment field gate. */
91
+ beforeDispatch?: (assignment: AssignmentFields) => Promise<GateResult>;
92
+ /** Called before an integration-branch merge; return the lease gate. */
93
+ beforeMerge?: (lease: IntegrationMergeLease) => Promise<GateResult>;
94
+ log: (level: "info" | "warn" | "error", msg: string) => void;
95
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @mstar-harness/engine — public entry (exports map `.` → `dist/engine.js`).
3
+ *
4
+ * Engine = importable library for deterministic harness checks; the CLI and
5
+ * OpenCode plugin consume it in-process. `core` is the shared type/version
6
+ * base, `path` implements harness path resolution + scaffold + gitignore
7
+ * checks, `status` implements the status.json schema, residual lifecycle,
8
+ * findings-cleanup gate and the tech-debt rollup port, `lease` implements
9
+ * the execution/merge lease state machines + same-host status write lock,
10
+ * `dispatch` implements the Assignment field contract, default-branch
11
+ * gate, QC seat mapping and tri-identity/anti-recursion prechecks, `lint`
12
+ * implements marker/TDD-triple/plan-quality/frontmatter/STRATEGY checks,
13
+ * `design-md` validates DESIGN.md token frontmatter + light/dark parity +
14
+ * completeness levels, `audit` validates audit Status blocks, redacts
15
+ * secrets and scaffolds audit-<date>/ plan dirs, and `compound` validates
16
+ * knowledge-doc schema, reference existence, index rows and the
17
+ * compound-refresh scope. `roles` validates the role reference mapping +
18
+ * parameter tables and the load-order contract, `host` detects the active
19
+ * host from tool shapes, resolves skill roots and defines the type-only
20
+ * `HostAdapter` contract, and `skill-authoring` lints frontmatter +
21
+ * 5-question bodies and resolves skill-relative asset paths.
22
+ */
23
+ export type { GateResult, Severity, ValidationResult } from "./core.js";
24
+ export { SEVERITY_ORDER, applyEnforcement, readHarnessVersion, readJson, resolveProjectRoot, writeJson } from "./core.js";
25
+ export type { HarnessKind, ResolveHarnessDirOptions, ResolveSpecsDirOptions } from "./path.js";
26
+ export { assertPlanWritingPath, emitGitignoreSnippet, resolveHarnessDir, resolveIterationDir, resolvePlanDir, resolveSddDir, resolveSpecsDir, scaffoldHarness, validateGitignore, } from "./path.js";
27
+ export type { ArchiveResult, FindingsCleanupMode, PlanRow, ResidualEntry, StatusDoc, TechDebtCheck, TechDebtRollup, TechDebtSummary, } from "./status.js";
28
+ export { archiveResiduals, findingsCleanupGate, normalizeSeverity, resolveCompassEnforcement, techDebtRollup, validatePlanRow, validateResidual, validateStatus, } from "./status.js";
29
+ export type { ClaimLeaseFields, ExecutionLease, ExecutionLeaseLocations, IntegrationMergeLease, LeaseTransition, LeaseVerifyResult, } from "./lease.js";
30
+ export { canSteal, claimLease, planExecutionLeaseLocations, releaseLease, sameHolderResume, validateExecutionLease, validateIntegrationMergeLease, verifyPlanExecutionLease, withStatusWriteLock, } from "./lease.js";
31
+ export type { AssignmentBranchForms, AssignmentFields, DefaultBranchOptions, EnforcementFlag, EnforcementSource, ExecutionModeToNOptions, ExecutionModeToNResult, ValidateAssignmentFieldsOptions, } from "./dispatch.js";
32
+ export { antiRecursionPrecheck, assertDefaultBranchProtected, assertTriIdentity, assignmentHeaderRegion, executionModeToN, isReadOnlyAssignmentRole, parseAssignmentBranchForms, parseAssignmentFields, parseBranchPolicyDirectOnBranch, parseEnforcementFlag, validateAssignmentFields, } from "./dispatch.js";
33
+ export type { BranchProbeOptions, L1PreDispatchInput, L2PreDispatchInput, QcAlignmentAssignment, QcSnapshotAssignment, WorktreeTrack, } from "./worktree.js";
34
+ export { assertBranchAlignment, assertControlVsFeaturePath, assertQcAlignment, l1PreDispatchCheck, l2PreDispatchCheck, singleReviewSnapshot, } from "./worktree.js";
35
+ export type { ImplementerSessionLedger, ReviewPackageOptions, SddWorkspaceOptions, StickyRulesInput, StickyRulesResult, TaskBriefOptions, } from "./sdd.js";
36
+ export { SddScriptError, assertBaseSha, implementerSessionStickyRules, readProgressLedger, reviewPackage, sddWorkspace, taskBrief, taskReportExists, } from "./sdd.js";
37
+ export type { CompassDoc, PhaseGateOptions, PhaseGateResult, PhaseTransition, } from "./iteration.js";
38
+ export { assertIndexRowObligations, evaluatePhaseGate, pushCadenceProbe, validateCompassFrontmatter, } from "./iteration.js";
39
+ export type { CompletenessItem, CompletenessLevel, CompletenessPlaceholder, CompletenessResult, DesignFrontmatter, } from "./design-md.js";
40
+ export { assertLightDarkParity, completenessLevel, parseDesignFrontmatter, validateDesignTokenFrontmatter, } from "./design-md.js";
41
+ export type { AuditCategory, AuditEffort, AuditFinding, AuditPriority, AuditRisk, RedactResult, ScaffoldAuditPlanOptions, ScaffoldAuditPlanResult, SecretFinding, } from "./audit.js";
42
+ export { AUDIT_CATEGORIES, AUDIT_EFFORTS, AUDIT_PRIORITIES, AUDIT_RISKS, redactSecrets, scaffoldAuditPlan, validateAuditStatusBlocks, } from "./audit.js";
43
+ export type { ReferenceCheckResult } from "./compound.js";
44
+ export { KNOWLEDGE_BUG_PROBLEM_TYPES, KNOWLEDGE_CATEGORY_MAP, KNOWLEDGE_KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_REQUIRED_FIELDS, KNOWLEDGE_RESOLUTION_TYPES, KNOWLEDGE_SEVERITIES, assertIndexRows, compoundRefreshScope, referenceExists, scopeGuard, validateSchemaYaml, } from "./compound.js";
45
+ export type { PlanQualityFinding, PlanQualityResult, SimplifyMarker, TemporaryMarker, TemporaryMarkerResult, } from "./lint.js";
46
+ export { assertSddTddTriple, findSimplifyMarkers, findTemporaryMarkers, lintSkillFrontmatter, lintStrategySections, planQualityBar, } from "./lint.js";
47
+ export type { DevTrackParam, QcReviewerParam, RoleFamily, RoleMappingEntry, RoleMappingOptions, } from "./roles.js";
48
+ export { DEV_TRACK_PARAMS, QC_REVIEWER_PARAMS, ROLE_MAPPING, SHARED_FAMILIES, lintLoadOrder, validateRoleMapping, } from "./roles.js";
49
+ export type { DetectResult, HostAdapter, HostId, SkillRootPaths, ToolSignal } from "./host.js";
50
+ export { detectHost, resolveSkillRoot } from "./host.js";
51
+ export type { FiveQuestionSection } from "./skill-authoring.js";
52
+ export { FIVE_QUESTION_SECTIONS, lintFiveQuestion, lintFrontmatter, resolveAssetPath, } from "./skill-authoring.js";
@@ -0,0 +1,88 @@
1
+ import type { GateResult, ValidationResult } from "./core.js";
2
+ import { type StatusDoc } from "./status.js";
3
+ /**
4
+ * Loose shape of a parsed delivery-compass.md frontmatter. All fields are
5
+ * `unknown` because documents come from YAML at runtime; validators narrow
6
+ * them.
7
+ */
8
+ export type CompassDoc = {
9
+ iteration_id?: unknown;
10
+ start_date?: unknown;
11
+ end_date?: unknown;
12
+ status?: unknown;
13
+ iteration_base_branch?: unknown;
14
+ target_branch?: unknown;
15
+ plans?: unknown;
16
+ [key: string]: unknown;
17
+ };
18
+ /** Where the iteration stands per the Phase transition gates table. */
19
+ export type PhaseTransition = "phase-2-execute" | "phase-3-close" | "phase-4-pr-delivery";
20
+ /**
21
+ * Git probe inputs for the §3.5 exit checklist. The engine never shells out
22
+ * to git; callers (CLI / host hooks) probe and pass values in.
23
+ */
24
+ export type PhaseGateOptions = {
25
+ /** `git branch --show-current` of the working checkout (Phase 3 runs on the integration branch). */
26
+ currentBranch?: string;
27
+ /** Expected `spec_integration_branch` (status.json metadata / compass Delivery Branch Policy). */
28
+ specIntegrationBranch?: string;
29
+ /** Resolved PR base branch for Phase 4 (§3.5 exit item 6). */
30
+ prBaseBranch?: string;
31
+ };
32
+ /**
33
+ * Result of the phase-transition gate evaluation. `entry`/`exit` are the
34
+ * checkable subsets of the §3.1 / §3.5 checklists; `ok`/`violations` are the
35
+ * gate verdict over the triggered transition:
36
+ * - not all plans Done → `phase-2-execute` (keep executing; gate passes).
37
+ * - all plans Done with missing checklist items → `phase-3-close` (Phase 3
38
+ * required; missing items listed in `violations`).
39
+ * - all plans Done and both checklists clean → `phase-4-pr-delivery`.
40
+ *
41
+ * Note (qc2 F-003): during the Phase-3 window `ok` is false because the
42
+ * §3.4 close items (`status: completed` + `end_date`) are only written at
43
+ * the END of close — the exit checklist gates Phase 4, not the Phase-3
44
+ * entry, so callers (e.g. the CLI, which exits 1) must treat that as "close
45
+ * work pending", not "don't enter Phase 3".
46
+ */
47
+ export type PhaseGateResult = {
48
+ transition: PhaseTransition;
49
+ allPlansDone: boolean;
50
+ /** §3.1 close entry checklist — checkable subset (HARD GATE before §3.2). */
51
+ entry: GateResult;
52
+ /** §3.5 close exit checklist — checkable subset (gate to Phase 4). */
53
+ exit: GateResult;
54
+ ok: boolean;
55
+ violations: ValidationResult[];
56
+ };
57
+ /**
58
+ * Validate a parsed delivery-compass.md frontmatter (mstar-iteration §1.3 +
59
+ * iteration-compass-template.md Fields guide). `end_date` is required only
60
+ * when `status: completed` (Phase 3 §3.4) and is a violation while the
61
+ * iteration is still `active`/`locked`.
62
+ */
63
+ export declare function validateCompassFrontmatter(doc: unknown): GateResult;
64
+ /**
65
+ * Evaluate the Phase transition gates (mstar-iteration Phase transition
66
+ * gates table): all compass-registered plans `Done` (per statusDoc plans[]
67
+ * status) → Phase 3 required, with the checkable subsets of the §3.1 entry
68
+ * and §3.5 exit checklists as missing-item violations.
69
+ *
70
+ * Pure function — git probes (`currentBranch`, `specIntegrationBranch`,
71
+ * `prBaseBranch`) come from the caller via `opts`.
72
+ */
73
+ export declare function evaluatePhaseGate(statusDoc: StatusDoc, compassDoc: CompassDoc, opts?: PhaseGateOptions): PhaseGateResult;
74
+ /**
75
+ * §5.1a push-cadence probe (HARD): never push the PR head while required CI
76
+ * is still queued/in_progress or an AI/bot review wave is running. Pure
77
+ * function — no external calls; callers probe CI / review state and pass the
78
+ * booleans. Fix locally early, push once the wave settles (§5.1a push gate
79
+ * 1 + 2).
80
+ */
81
+ export declare function pushCadenceProbe(ciRunning: boolean, reviewWaveActive: boolean): GateResult;
82
+ /**
83
+ * §1.4 index obligations: one row per iteration in `{ITERATION_DIR}/README.md`
84
+ * (table header on first creation). Iterations are discovered as subdirectories
85
+ * of `iterationsDir` containing `delivery-compass.md`. Returns violations for
86
+ * a missing README, a missing header, and missing per-iteration rows.
87
+ */
88
+ export declare function assertIndexRowObligations(iterationsDir: string): GateResult;
@@ -0,0 +1,193 @@
1
+ import type { GateResult, ValidationResult } from "./core.js";
2
+ import type { PlanRow } from "./status.js";
3
+ /**
4
+ * `plans[].execution_lease` (v1) — see spec header. Extra fields (e.g. the
5
+ * real control data's `base_sha`) are allowed and preserved.
6
+ */
7
+ export type ExecutionLease = {
8
+ holder: string;
9
+ claimed_at: string;
10
+ worktree_path: string;
11
+ working_branch: string;
12
+ session_label?: string;
13
+ [key: string]: unknown;
14
+ };
15
+ /**
16
+ * Root `metadata.integration_merge_lease` (v1) — see spec header. Absent =
17
+ * unclaimed; writers delete the key on release (never `null`/tombstone).
18
+ */
19
+ export type IntegrationMergeLease = {
20
+ holder: string;
21
+ claimed_at: string;
22
+ plan_id: string;
23
+ source_branch: string;
24
+ target_branch: string;
25
+ session_label?: string;
26
+ [key: string]: unknown;
27
+ };
28
+ /** Assignment-side fields written into an `execution_lease` at claim time. */
29
+ export type ClaimLeaseFields = {
30
+ worktree_path: string;
31
+ working_branch: string;
32
+ session_label?: string;
33
+ };
34
+ /**
35
+ * Result of a pure lease transition on one plan row. `row` is the resulting
36
+ * row (unchanged when `ok` is false); `outcome` distinguishes a fresh claim
37
+ * from a same-holder resume / a release.
38
+ */
39
+ export type LeaseTransition = {
40
+ ok: boolean;
41
+ row: PlanRow;
42
+ outcome?: "claimed" | "resumed" | "released";
43
+ violations: ValidationResult[];
44
+ };
45
+ /**
46
+ * Validate one `plans[].execution_lease` object (status-and-residuals.md
47
+ * § `plans[].execution_lease`): required `holder` / `claimed_at` /
48
+ * `worktree_path` (absolute) / `working_branch`; optional `session_label`
49
+ * display-only. `null` and tombstone objects are invalid — writers delete
50
+ * the key on release, never write `null` (§ Agent prohibitions). Extra
51
+ * fields are allowed (the real control lease carries `base_sha`).
52
+ */
53
+ export declare function validateExecutionLease(lease: unknown): GateResult;
54
+ /**
55
+ * Validate one root `metadata.integration_merge_lease` object
56
+ * (status-and-residuals.md § Root `metadata.integration_merge_lease` (v1)):
57
+ * required `holder` / `claimed_at` / `plan_id` / `source_branch` /
58
+ * `target_branch`; optional `session_label`. Absent = unclaimed; `null` and
59
+ * tombstone objects are invalid (writers delete the key on release).
60
+ * Integration merges into `spec_integration_branch` are serial — one holder
61
+ * at a time (phase-2-worktree-lease.md § Integration merge lease).
62
+ */
63
+ export declare function validateIntegrationMergeLease(lease: unknown): GateResult;
64
+ /**
65
+ * Claim-before-`InProgress` transition (status-and-residuals.md
66
+ * § Claim-before-`InProgress`; phase-2-worktree-lease.md § Execution lease).
67
+ *
68
+ * - No lease + status `Todo`/`Blocked` → claim: status becomes `InProgress`
69
+ * and the full `execution_lease` is written in one update (`claimed_at`
70
+ * = now, RFC 3339 UTC with `Z`).
71
+ * - Lease with the **same** `holder` → resume: the stored lease is preserved
72
+ * verbatim, but `worktree_path`/`working_branch` must match the Assignment
73
+ * (`verify-held-lease`) or the resume is refused.
74
+ * - Lease with a **different** `holder` → refused: no timestamp makes it
75
+ * stealable.
76
+ * - `InProgress` **without** a lease → orphan: refused; never invent a
77
+ * lease (§ Orphan recovery — recovery is PM/human-owned).
78
+ * - `null`/tombstone stored lease → refused, not silently replaced
79
+ * (§ Agent prohibitions).
80
+ * - The fields to be written are validated via `validateExecutionLease`
81
+ * **before** the Todo/Blocked → InProgress transition commits: a relative
82
+ * `worktree_path` or missing fields are rejected without mutating the row
83
+ * (the written lease must itself pass the validator).
84
+ *
85
+ * Pure: returns the resulting row; the caller performs the locked
86
+ * read-check-replace-verify around `status.json` and persists it.
87
+ */
88
+ export declare function claimLease(row: PlanRow, holder: string, fields: ClaimLeaseFields): LeaseTransition;
89
+ /**
90
+ * Release transition — deletes `execution_lease` entirely, never writes
91
+ * `null` (status-and-residuals.md § "Hold, release, and override" + § Agent
92
+ * prohibitions). Requires the **same-session holder**: when `holder` differs
93
+ * from the stored lease `holder`, the release is refused and the row is left
94
+ * unmodified — a different holder must Blocked, never released by another
95
+ * session (no timestamp makes a lease stealable). Idempotent when no lease
96
+ * is present. The `Done` authority deletes the lease in the same
97
+ * complete-file update as `status: "Done"` — **only after** successful
98
+ * integration merge into `spec_integration_branch` (§ Integration merge
99
+ * protocol); the caller enforces that ordering around the locked update.
100
+ */
101
+ export declare function releaseLease(row: PlanRow, holder: string): LeaseTransition;
102
+ /**
103
+ * Same-holder resume check (status-and-residuals.md § Claim-before-`InProgress`
104
+ * #2): `true` iff the stored lease `holder` equals the session `holder`.
105
+ * Verify-held-lease (worktree_path/working_branch vs the Assignment) is the
106
+ * caller's check via `claimLease`.
107
+ */
108
+ export declare function sameHolderResume(lease: unknown, holder: string): boolean;
109
+ /**
110
+ * Steal decision (status-and-residuals.md § Agent prohibitions + § "Hold,
111
+ * release, and override"): an active lease MUST NOT be stolen — `false`
112
+ * unless the **current-turn user explicitly authorizes** the override
113
+ * (`opts.userOverride: true`). The caller MUST still append an audit entry
114
+ * to `plans[].notes` (timestamp, prior holder, new holder/release, user
115
+ * authorized). Agents MUST NOT infer override from age, inactivity, `Blocked`
116
+ * status, or a failed session.
117
+ */
118
+ export declare function canSteal(lease: unknown, holder: string, opts?: {
119
+ userOverride?: boolean;
120
+ }): boolean;
121
+ /** Execution lease locations found on one plan row (SSOT + legacy read-compat). */
122
+ export type ExecutionLeaseLocations = {
123
+ /** SSOT location: `plans[].execution_lease`. */
124
+ row: unknown;
125
+ /** Legacy/hand-written read-compat location: `plans[].metadata.execution_lease`. */
126
+ metadata: unknown;
127
+ };
128
+ export declare function planExecutionLeaseLocations(row: Record<string, unknown>): ExecutionLeaseLocations;
129
+ export type LeaseVerifyResult = {
130
+ ok: boolean;
131
+ violations: ValidationResult[];
132
+ /** The lease chosen for validation (row-level wins) — absent when neither location has one. */
133
+ lease?: unknown;
134
+ };
135
+ /**
136
+ * Verify a plan's `execution_lease` across its two possible locations
137
+ * (status-and-residuals.md § `plans[].execution_lease`; ADR
138
+ * 2026-07-22-iteration-worktree-plan-lease.md A3 — the plan row is the
139
+ * claim/hold/release SSOT):
140
+ * - Row-level `plans[].execution_lease` only, valid → OK.
141
+ * - Metadata-only (`plans[].metadata.execution_lease`) → high-severity
142
+ * `lease.verify.non-ssot-location`: the metadata location is a
143
+ * legacy/hand-written read-compat fallback, NOT equivalent to SSOT
144
+ * success. Always a FAIL (non-zero exit) with the lease shape still
145
+ * validated and reported.
146
+ * - Both locations present → `lease.verify.dual-write`: the row-level lease
147
+ * wins and is validated; the metadata copy must be deleted.
148
+ * - Neither present → `lease.verify.missing` (non-InProgress) /
149
+ * `lease.verify.orphan` (InProgress).
150
+ *
151
+ * Kept in the engine so every host hook / CLI entry / Slice-2+ consumer
152
+ * imports ONE gate (CLI `mstar lease verify` is a thin wrapper).
153
+ */
154
+ export declare function verifyPlanExecutionLease(row: Record<string, unknown>, planId: string): LeaseVerifyResult;
155
+ /**
156
+ * Same-host exclusive write lock around `status.json` coordination writes
157
+ * (status-and-residuals.md § "Same-host exclusive write lock (control
158
+ * status.json)"; phase-2-worktree-lease.md § "Same-host exclusive write
159
+ * lock"). Lease mutations and plan-status transitions that touch leases MUST
160
+ * run inside this lock for the full read-check-replace-verify sequence.
161
+ *
162
+ * Acquires by atomic `mkdir` on `<status dir>/.status-write.lockdir/`
163
+ * (success acquires; existing dir → another writer holds the lock). While
164
+ * another writer holds it, wait up to `timeoutMs` (default 30s) and then
165
+ * throw (Blocked) — the lockdir is never removed for another holder.
166
+ *
167
+ * Ownership guard (double-unlock safety): the lockdir's `(dev, ino)` is
168
+ * captured at acquisition; `finally` re-stats the path and removes the
169
+ * directory ONLY when the identity is unchanged. When `fn` itself removed
170
+ * the lockdir (e.g. explicit rollback), or another writer replaced it with
171
+ * a fresh lockdir before this writer's `finally` ran, the removal is
172
+ * skipped — a second writer's lock is never destroyed.
173
+ *
174
+ * Reentrancy: a nested acquisition on the same lockdir within the same
175
+ * async context (i.e. `fn` calling `withStatusWriteLock` on the same
176
+ * status.json) throws immediately instead of waiting out the timeout.
177
+ *
178
+ * Crash diagnosis: a `holder.pid` file (acquiring process id) is written
179
+ * inside the lockdir on acquisition and removed on release. A hard crash
180
+ * between `mkdirSync` and release leaks the lockdir; the timeout error
181
+ * message names the recovery step (remove the lockdir when no writer is
182
+ * alive).
183
+ *
184
+ * simplify: mkdir lockdir is the SSOT-documented alternative to `flock`
185
+ * (`{HARNESS_DIR}/.status-write.lock`) — Bun 1.2 exposes no `node:fs`
186
+ * flock/flockSync, so the advisory-file variant is unavailable here. Unlike
187
+ * flock, a hard process crash leaks the lockdir; swap to flock when the
188
+ * runtime provides it.
189
+ */
190
+ export declare function withStatusWriteLock<T>(statusPath: string, fn: () => T | Promise<T>, opts?: {
191
+ timeoutMs?: number;
192
+ pollMs?: number;
193
+ }): Promise<T>;
package/dist/lint.d.ts ADDED
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Engine lint module — deterministic lint checks ported from skill prose.
3
+ *
4
+ * Roadmap: `.harness/references/skill-programmatic-roadmap.md` §8.2 `lint`
5
+ * row + §4.5 Lint layer (`simplify:` / `temporary` marker presence; SDD TDD
6
+ * triple in completion reports; plan-quality-bar checks; skill frontmatter
7
+ * contract; STRATEGY.md required sections). Skill text stays the semantic
8
+ * SSOT (roadmap D5) — this module implements the deterministic subset and
9
+ * never forks semantics.
10
+ *
11
+ * Spec sources (each function cites the source section):
12
+ * - simplify:/temporary markers: `mstar-coding-behavior` SKILL.md § Simplicity
13
+ * First → "Simplification markers": a deliberate shortcut with a known
14
+ * ceiling is marked with a `simplify:` comment naming the ceiling and the
15
+ * upgrade path; a workaround is labeled `simplify:` / `temporary`, explains
16
+ * why, and records the removal path in the plan/status artifact before the
17
+ * task is claimed complete.
18
+ * - SDD TDD triple: `mstar-coding-behavior` SKILL.md § Integration Notes —
19
+ * completion evidence must include the TDD triple (test file(s), command,
20
+ * output) in `task-N-report.md`; `mstar-sdd/references/file-handoffs.md` —
21
+ * fix subagents append covering test file(s), command run, output.
22
+ * - Plan quality bar: `mstar-plan-artifacts/references/plan-quality-bar.md`
23
+ * § Quality checklist + `templates/plan.main.md` self-review
24
+ * ("Placeholder scan: no TBD").
25
+ * - Skill frontmatter contract: `mstar-skill-authoring` SKILL.md § Frontmatter
26
+ * Contract — `name` stable lowercase-hyphen; `description` is the trigger
27
+ * contract (not a workflow summary), third person.
28
+ * - STRATEGY.md structure: `mstar-strategy` SKILL.md § STRATEGY.md structure —
29
+ * six required sections.
30
+ *
31
+ * Enforcement depth: roadmap §8.5 C4 — v1 lints are non-blocking
32
+ * `ValidationResult`s; callers surface them as warnings.
33
+ *
34
+ * Severity mapping: structural gaps (missing name/description/sections,
35
+ * placeholder tokens, TDD-triple gaps, un-tracked temporary markers) are
36
+ * `medium`; style heuristics (pronouns, workflow-summary shape) are `low`.
37
+ */
38
+ import type { GateResult } from "./core.js";
39
+ /**
40
+ * A `simplify:` marker found in a file, with its 1-based line number and the
41
+ * trimmed comment line (lint reporting).
42
+ */
43
+ export type SimplifyMarker = {
44
+ line: number;
45
+ text: string;
46
+ };
47
+ /**
48
+ * Find `simplify:` marker comments (mstar-coding-behavior § Simplicity First
49
+ * → "Simplification markers": a deliberate shortcut with a known ceiling —
50
+ * global lock, O(n²) scan, naive heuristic — is marked with a `simplify:`
51
+ * comment naming the ceiling and the upgrade path).
52
+ *
53
+ * Heuristic (documented, conservative): a marker is a line where a comment
54
+ * introducer (`//`, `/*`, `*`, `#`, `;`, `--`) precedes `simplify:` —
55
+ * case-insensitive, any column (leading or trailing comments). Pure prose
56
+ * like "we simplify: the interface" carries no comment introducer and is
57
+ * never reported. No judgment about whether the marker actually names a
58
+ * ceiling/upgrade path — discovery only; callers may inspect `text`.
59
+ */
60
+ export declare function findSimplifyMarkers(fileText: string): SimplifyMarker[];
61
+ /**
62
+ * A `temporary` marker found in a file. `removalPath` is the recorded
63
+ * plan/status artifact reference (first pattern match, see
64
+ * `findTemporaryMarkers`), or `null` when the marker names no removal path.
65
+ */
66
+ export type TemporaryMarker = {
67
+ line: number;
68
+ text: string;
69
+ removalPath: string | null;
70
+ };
71
+ /** Result of `findTemporaryMarkers`: the gate verdict plus the markers found
72
+ * (so callers can both lint and report). */
73
+ export type TemporaryMarkerResult = GateResult & {
74
+ markers: TemporaryMarker[];
75
+ };
76
+ /**
77
+ * Find `temporary` label comments and check each carries a recorded removal
78
+ * path (plan/status artifact reference). Markers lacking a removal path are
79
+ * violations: `lint.temporary.no-removal-path` (mstar-coding-behavior §
80
+ * Simplicity First — "If a workaround is unavoidable, label it `simplify:` /
81
+ * `temporary`, explain why, and record the removal path in the plan/status
82
+ * artifact before claiming the task complete").
83
+ *
84
+ * Heuristic (documented, conservative): a marker is a comment-context line
85
+ * containing the word `temporary` (case-insensitive, word-boundary — so
86
+ * "temporarily" is not a label). Removal-path detection uses the pattern set
87
+ * above; a version mention alone ("remove in v2.0.0") is NOT a plan/status
88
+ * artifact reference and is reported, per the strict convention wording.
89
+ *
90
+ * Accepted false-positive (documented trade-off): the `;` / `--` comment
91
+ * introducers (Lisp/SQL comment syntax) can match code lines such as
92
+ * `foo(); temporary = 1;` — the `;` introducer + `\s*temporary\b` looks
93
+ * like a marker even though `temporary` is a variable name. Errs toward
94
+ * flagging (a violation prompts a removal path, which the code line won't
95
+ * carry); do not special-case it without a real FP report.
96
+ */
97
+ export declare function findTemporaryMarkers(fileText: string): TemporaryMarkerResult;
98
+ /**
99
+ * Assert the SDD TDD triple is present in a `task-N-report.md` text
100
+ * (mstar-coding-behavior § Integration Notes — "completion evidence must
101
+ * include TDD triple — test file(s), command, output — in task-N-report.md";
102
+ * mstar-sdd/references/file-handoffs.md — fix subagents append covering test
103
+ * file(s), command run, output).
104
+ *
105
+ * One violation per missing part:
106
+ * - `lint.sdd-tdd.missing-tests` — no test file reference
107
+ * - `lint.sdd-tdd.missing-command` — no runnable command
108
+ * - `lint.sdd-tdd.missing-output` — no output evidence
109
+ *
110
+ * Heuristics (documented, conservative — tuned so prose alone never counts):
111
+ * - tests: a `.test.<ext>` / `.spec.<ext>` path, or the phrase "test file(s)"
112
+ * (the handoff template's exact header). "I added tests" without a file or
113
+ * the phrase does not count.
114
+ * - command: a `$`-prefixed line, or a known runner invocation (bun/pnpm/
115
+ * npm/yarn/npx/bunx test|run|exec, npx/bunx exec, tsc/vitest/jest/mocha/
116
+ * pytest/go test/cargo test). Prose "run the tests" names no runner and
117
+ * does not count (plan-quality-bar marks it a weak step anyway).
118
+ * - output: check marks, PASS/FAIL tokens, counts ("12 pass"), `N ok` /
119
+ * TAP `ok N` / "all ok" verdicts, exit-code statements. Bare prose
120
+ * `OK`/`ERROR` ("OK, moving on") does NOT count — output evidence must
121
+ * look like output. Line-based and fence-insensitive: real output usually
122
+ * lives in fenced blocks, so fence content is scanned too.
123
+ */
124
+ export declare function assertSddTddTriple(reportText: string): GateResult;
125
+ /**
126
+ * One placeholder occurrence found by `planQualityBar`.
127
+ */
128
+ export type PlanQualityFinding = {
129
+ /** Normalized token: `TBD`, `TODO`, `TBA`, or `...`. */
130
+ token: string;
131
+ /** 1-based line number. */
132
+ line: number;
133
+ /** Trimmed source line. */
134
+ text: string;
135
+ };
136
+ /** Result of `planQualityBar`: the gate verdict plus structured findings. */
137
+ export type PlanQualityResult = GateResult & {
138
+ findings: PlanQualityFinding[];
139
+ };
140
+ /**
141
+ * Plan quality bar — placeholder scan (mstar-plan-artifacts
142
+ * `references/plan-quality-bar.md` § Quality checklist + `templates/
143
+ * plan.main.md` self-review "Placeholder scan: no TBD"). Every placeholder
144
+ * token found becomes one `lint.plan-quality.placeholder` violation whose
145
+ * message lists the token and location.
146
+ *
147
+ * Heuristic (documented, conservative):
148
+ * - tokens: `TBD`, `TODO`, `TBA` (case-insensitive, word-boundary, plural
149
+ * forms included) and the prose ellipsis `...`. One finding per line per
150
+ * token (a line with two TBDs yields one finding).
151
+ * - negation guard: a token preceded by a negation word (`no/not/without/
152
+ * none`) in the same segment (split at `(`/`[`/`{`/`.`/`;`/`,`/line
153
+ * start) is an absence assertion ("no TBD/placeholder/TODO" states the
154
+ * rule), not a placeholder — not flagged.
155
+ * - exemptions: fenced code blocks (```` ``` ```` / `~~~`) and inline code
156
+ * spans are skipped — `...` in a file-list or example is not a placeholder.
157
+ * - out of scope (judgment stays prompt): "add tests" without code, and
158
+ * `FIXME`/`XXX` code markers.
159
+ */
160
+ export declare function planQualityBar(planText: string): PlanQualityResult;
161
+ /**
162
+ * Lint a skill file's frontmatter against the mstar-skill-authoring §
163
+ * Frontmatter Contract:
164
+ * - `name` — stable, lowercase-hyphen (`example-skill`);
165
+ * - `description` — the trigger contract, third person, not a workflow
166
+ * summary.
167
+ *
168
+ * Accepts a full document (leading `---`-fenced block is parsed) or a bare
169
+ * frontmatter body (`name:`/`description:` lines at the start). Violations:
170
+ * - `lint.frontmatter.missing` — no frontmatter block found
171
+ * - `lint.frontmatter.name.missing` — `name` absent
172
+ * - `lint.frontmatter.name.format` — `name` not lowercase-hyphen
173
+ * - `lint.frontmatter.description.missing` — `description` absent/empty
174
+ * - `lint.frontmatter.description.person` — first/second-person pronoun in
175
+ * the description (third-person heuristic, low severity)
176
+ * - `lint.frontmatter.description.workflow` — description reads as a
177
+ * workflow summary (verb-start or paragraph-length heuristic, low
178
+ * severity)
179
+ *
180
+ * Heuristics (documented, conservative; corpus regression tests in
181
+ * lint.test.ts cover the 20 real skill frontmatters):
182
+ * - pronouns: `I`/`we`/`you`/`my`/`our`/`your`/`us`, word-boundary,
183
+ * case-insensitive, after stripping quoted and backticked spans; `I/`
184
+ * (I/O) and all-caps `US` exempt.
185
+ * - workflow shape: description starts with a workflow verb ("Explains how
186
+ * …", "Describes …") — the contract's own bad example — or exceeds 120
187
+ * words (corpus max 114). Bold/quote prefixes are stripped before the
188
+ * verb check. No content judgment (e.g. whether the trigger is narrow
189
+ * enough) — that stays prompt.
190
+ */
191
+ export declare function lintSkillFrontmatter(frontmatterText: string): GateResult;
192
+ /**
193
+ * Lint a STRATEGY.md document for the six required section headings
194
+ * (mstar-strategy § STRATEGY.md structure: Vision, What we build, What we
195
+ * don't build, Guiding Principles, Technology Direction, Decision Log).
196
+ * Optional sections (Current Focus, Risks & Mitigations, Competitive
197
+ * Context) are not required.
198
+ *
199
+ * Heuristic (documented, conservative): a section is a Markdown heading
200
+ * (`#`–`######`) whose text matches a required name exactly, case-
201
+ * insensitively, after stripping emphasis/backticks (so `## VISION`,
202
+ * `### What We Don't Build` all count). Partial matches ("Vision (short)")
203
+ * and other heading levels do not count. Each missing section is one
204
+ * `lint.strategy.missing-section` violation naming the heading.
205
+ */
206
+ export declare function lintStrategySections(docText: string): GateResult;