@mstar-harness/engine 2.1.1 → 2.2.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 +1 -1
- package/dist/engine.js +40 -9
- package/dist/host.d.ts +24 -14
- package/dist/path.d.ts +21 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ const version = readHarnessVersion(); // "1.8.8" — monorepo root package.json
|
|
|
19
19
|
## Scope
|
|
20
20
|
|
|
21
21
|
- Importable library only — **no `bin`**; the CLI (`@mstar-harness/cli`) wraps engine functions as thin `mstar …` subcommands.
|
|
22
|
-
- Dependencies: `node:*` only (zero external runtime deps — all validators hand-rolled; ajv pruned as phantom
|
|
22
|
+
- Dependencies: `node:*` only (zero external runtime deps — all validators hand-rolled; ajv and zod were pruned as phantom dependencies).
|
|
23
23
|
- Skill prose stays authoritative; engine exports are the machine-checkable mirror of the rules the `mstar-*` skills state.
|
|
24
24
|
|
|
25
25
|
## License
|
package/dist/engine.js
CHANGED
|
@@ -83,24 +83,52 @@ function readHarnessVersion() {
|
|
|
83
83
|
}
|
|
84
84
|
// src/path.ts
|
|
85
85
|
import { mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
86
|
+
import { execFileSync } from "node:child_process";
|
|
86
87
|
import { basename as basename2, dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "node:path";
|
|
87
88
|
function resolveHarnessDir(startDir = process.cwd(), opts = {}) {
|
|
88
89
|
const start = resolve2(startDir);
|
|
89
90
|
const explicit = opts.harnessDir ?? process.env.MSTAR_HARNESS_DIR;
|
|
90
91
|
if (explicit)
|
|
91
92
|
return resolve2(start, explicit);
|
|
93
|
+
const boundary = resolve2(start, opts.workspaceRoot ?? defaultWorkspaceRoot(start));
|
|
92
94
|
let dir = start;
|
|
93
95
|
for (;; ) {
|
|
96
|
+
if (!isAtOrBelow(dir, boundary))
|
|
97
|
+
return null;
|
|
94
98
|
for (const candidate of [join2(dir, ".mstar"), join2(dir, ".agents"), join2(dir, ".plans"), join2(dir, "plans")]) {
|
|
95
99
|
if (isDirectory(candidate))
|
|
96
100
|
return candidate;
|
|
97
101
|
}
|
|
102
|
+
if (dir === boundary)
|
|
103
|
+
return null;
|
|
98
104
|
const parent = dirname2(dir);
|
|
99
105
|
if (parent === dir)
|
|
100
106
|
return null;
|
|
101
107
|
dir = parent;
|
|
102
108
|
}
|
|
103
109
|
}
|
|
110
|
+
function defaultWorkspaceRoot(startDir) {
|
|
111
|
+
try {
|
|
112
|
+
const cdup = execFileSync("git", ["rev-parse", "--show-cdup"], {
|
|
113
|
+
cwd: startDir,
|
|
114
|
+
encoding: "utf8",
|
|
115
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
116
|
+
}).trim();
|
|
117
|
+
if (!cdup)
|
|
118
|
+
return startDir;
|
|
119
|
+
let boundary = startDir;
|
|
120
|
+
for (const segment of cdup.split(/[\\/]/)) {
|
|
121
|
+
if (segment && segment !== ".")
|
|
122
|
+
boundary = dirname2(boundary);
|
|
123
|
+
}
|
|
124
|
+
return resolve2(boundary);
|
|
125
|
+
} catch {}
|
|
126
|
+
return startDir;
|
|
127
|
+
}
|
|
128
|
+
function isAtOrBelow(dir, root) {
|
|
129
|
+
const rel = relative(root, dir);
|
|
130
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
131
|
+
}
|
|
104
132
|
function resolveSpecsDir(harnessDir, opts = {}) {
|
|
105
133
|
const harness = resolve2(harnessDir);
|
|
106
134
|
const repoRoot = dirname2(harness);
|
|
@@ -1150,7 +1178,7 @@ function techDebtRollup(docOrPath) {
|
|
|
1150
1178
|
return { computed, stored, checks, overall };
|
|
1151
1179
|
}
|
|
1152
1180
|
// src/worktree.ts
|
|
1153
|
-
import { execFileSync } from "node:child_process";
|
|
1181
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1154
1182
|
import { existsSync as existsSync3 } from "node:fs";
|
|
1155
1183
|
import { isAbsolute as isAbsolute3, resolve as resolve5 } from "node:path";
|
|
1156
1184
|
var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
|
|
@@ -1173,7 +1201,7 @@ function probeBranch(worktreePath, opts) {
|
|
|
1173
1201
|
return { branch: precomputed };
|
|
1174
1202
|
const timeout = opts.timeoutMs ?? probeTimeoutMs();
|
|
1175
1203
|
try {
|
|
1176
|
-
const stdout =
|
|
1204
|
+
const stdout = execFileSync2(opts.gitPath ?? "git", ["-C", worktreePath, "branch", "--show-current"], {
|
|
1177
1205
|
encoding: "utf8",
|
|
1178
1206
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1179
1207
|
timeout
|
|
@@ -1302,7 +1330,7 @@ function singleReviewSnapshot(assignments) {
|
|
|
1302
1330
|
return gate(violations);
|
|
1303
1331
|
}
|
|
1304
1332
|
// src/sdd.ts
|
|
1305
|
-
import { execFileSync as
|
|
1333
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1306
1334
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, realpathSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1307
1335
|
import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute4, join as join5, resolve as resolve6 } from "node:path";
|
|
1308
1336
|
class SddScriptError extends Error {
|
|
@@ -1330,7 +1358,7 @@ function isFile(file) {
|
|
|
1330
1358
|
var GIT_CAPTURE_MAX_BYTES = 64 * 1024 * 1024;
|
|
1331
1359
|
function gitOut(cwd, args) {
|
|
1332
1360
|
try {
|
|
1333
|
-
return
|
|
1361
|
+
return execFileSync3("git", args, {
|
|
1334
1362
|
cwd,
|
|
1335
1363
|
encoding: "utf8",
|
|
1336
1364
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -1463,7 +1491,7 @@ function reviewPackage(base, head, outFile, opts = {}) {
|
|
|
1463
1491
|
const cwd = opts.cwd ?? process.cwd();
|
|
1464
1492
|
const verifyRef = (ref, what) => {
|
|
1465
1493
|
try {
|
|
1466
|
-
|
|
1494
|
+
execFileSync3("git", ["rev-parse", "--verify", "--quiet", ref], { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
1467
1495
|
} catch {
|
|
1468
1496
|
throw new SddScriptError(`bad ${what}: ${ref}`, 2);
|
|
1469
1497
|
}
|
|
@@ -1483,7 +1511,7 @@ function reviewPackage(base, head, outFile, opts = {}) {
|
|
|
1483
1511
|
const shortHead = gitOut(cwd, ["rev-parse", "--short", head]) ?? head;
|
|
1484
1512
|
out = join5(sddDir, `review-${shortBase}..${shortHead}.diff`);
|
|
1485
1513
|
}
|
|
1486
|
-
const run = (args) =>
|
|
1514
|
+
const run = (args) => execFileSync3("git", args, { cwd, maxBuffer: GIT_CAPTURE_MAX_BYTES });
|
|
1487
1515
|
const parts = [
|
|
1488
1516
|
Buffer.from(`# Review package: ${base}..${head}
|
|
1489
1517
|
|
|
@@ -1507,7 +1535,7 @@ function assertBaseSha(ref, opts = {}) {
|
|
|
1507
1535
|
throw new SddScriptError(`assertBaseSha: BASE must be a commit SHA (full or prefix); got ${JSON.stringify(ref)}. ` + "Never use HEAD~1 as review BASE (multi-commit tasks truncate).", 2);
|
|
1508
1536
|
}
|
|
1509
1537
|
try {
|
|
1510
|
-
|
|
1538
|
+
execFileSync3("git", ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], {
|
|
1511
1539
|
cwd: opts.cwd,
|
|
1512
1540
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1513
1541
|
});
|
|
@@ -3377,6 +3405,8 @@ function detectHost(signals) {
|
|
|
3377
3405
|
return "opencode";
|
|
3378
3406
|
if (s.has("task_agent_batch") || s.has("ask") || s.has("hub"))
|
|
3379
3407
|
return "omp";
|
|
3408
|
+
if (s.has("subagent"))
|
|
3409
|
+
return "dsh";
|
|
3380
3410
|
if (s.has("AgentSwarm"))
|
|
3381
3411
|
return "kimi";
|
|
3382
3412
|
if (s.has("Agent") || s.has("AskUserQuestion") || s.has("EnterPlanMode") || s.has("TodoWrite"))
|
|
@@ -3400,9 +3430,10 @@ function resolveSkillRoot(host, paths) {
|
|
|
3400
3430
|
case "kimi":
|
|
3401
3431
|
case "zcode":
|
|
3402
3432
|
return `./skills/${skill}${suffix}`;
|
|
3403
|
-
case "pi":
|
|
3404
3433
|
case "dsh":
|
|
3405
|
-
return
|
|
3434
|
+
return `$DSH_BUNDLED_SKILL_DIR/${skill}${suffix}`;
|
|
3435
|
+
case "pi":
|
|
3436
|
+
return `deferred: pi has no plugin API in v1 — skill-root resolution lands with its adapter (roadmap §8.4)`;
|
|
3406
3437
|
}
|
|
3407
3438
|
}
|
|
3408
3439
|
// src/skill-authoring.ts
|
package/dist/host.d.ts
CHANGED
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
* resolution.
|
|
12
12
|
* - `.harness/references/skill-programmatic-roadmap.md` §8.4 — the
|
|
13
13
|
* `HostAdapter` shared contract (all hooks optional; no concrete adapters
|
|
14
|
-
* in the engine; pi
|
|
14
|
+
* in the engine; pi deferred).
|
|
15
|
+
* - `.harness/references/dsh-adapter-roadmap.md` §4 D5 (this iteration) —
|
|
16
|
+
* the dsh detection row + skill-root form; mirror text lands in the
|
|
17
|
+
* mstar-host skill when this module is upstreamed.
|
|
15
18
|
*
|
|
16
19
|
* UX judgment (ambiguous-host fallback reasoning, plan-mode bridges) stays
|
|
17
20
|
* prompt — this module only turns tool shapes into a host id.
|
|
@@ -19,18 +22,19 @@
|
|
|
19
22
|
import type { GateResult, ValidationResult } from "./core.js";
|
|
20
23
|
import type { AssignmentFields } from "./dispatch.js";
|
|
21
24
|
import type { IntegrationMergeLease } from "./lease.js";
|
|
22
|
-
/** All hosts the engine knows about (roadmap §8.4 host union). `pi`
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
+
/** All hosts the engine knows about (roadmap §8.4 host union). `pi` has no
|
|
26
|
+
* plugin API in v1 — it appears in the union (and in `HostAdapter.host`) but
|
|
27
|
+
* is never detected and gets no adapter. `dsh` is detected and resolved
|
|
28
|
+
* (roadmap §4 D5, this iteration); its adapter ships in the dsh plugin. */
|
|
25
29
|
export type HostId = "opencode" | "omp" | "pi" | "dsh" | "cursor" | "codex" | "kimi" | "zcode";
|
|
26
|
-
/** Result of `detectHost`: one of the
|
|
30
|
+
/** Result of `detectHost`: one of the seven known hosts or `ambiguous`
|
|
27
31
|
* (prompt judgment then applies per mstar-host). */
|
|
28
|
-
export type DetectResult = "opencode" | "omp" | "cursor" | "codex" | "kimi" | "zcode" | "ambiguous";
|
|
32
|
+
export type DetectResult = "opencode" | "omp" | "dsh" | "cursor" | "codex" | "kimi" | "zcode" | "ambiguous";
|
|
29
33
|
/** Tool-shape signal tokens accepted by `detectHost`, derived from the
|
|
30
34
|
* mstar-host detection table. Plan-mode extras (CreatePlan/SwitchMode) and
|
|
31
35
|
* Browser-plugin tools are documented in the table but are not part of the
|
|
32
36
|
* 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";
|
|
37
|
+
export type ToolSignal = "subagent_type" | "question" | "task_subagent" | "task_agent_batch" | "ask" | "hub" | "subagent" | "Agent" | "AgentSwarm" | "AskUserQuestion" | "EnterPlanMode" | "TodoWrite" | "plan_slash" | "goal" | "functions.*" | "tool_search";
|
|
34
38
|
/**
|
|
35
39
|
* Detect the active host from session tool shapes, per the ordered
|
|
36
40
|
* mstar-host table (ported verbatim):
|
|
@@ -40,14 +44,19 @@ export type ToolSignal = "subagent_type" | "question" | "task_subagent" | "task_
|
|
|
40
44
|
* | `subagent_type` (Task param; plan mode + CreatePlan/SwitchMode) | cursor |
|
|
41
45
|
* | `question`, or `task_subagent` (task tool, singular subagent, no batch) | opencode |
|
|
42
46
|
* | `task_agent_batch` (task tool, agent/tasks[] batch), `ask`, `hub` | omp |
|
|
47
|
+
* | `subagent` (dsh's model-facing delegation tool) | dsh |
|
|
43
48
|
* | `Agent`/`AskUserQuestion`/`EnterPlanMode` + `AgentSwarm` (Kimi-only) | kimi |
|
|
44
49
|
* | `Agent`/`AskUserQuestion`/`EnterPlanMode`/`TodoWrite`, no `AgentSwarm` | zcode |
|
|
45
50
|
* | `/plan`, `/goal`; Goal tools; `functions.*` namespaces; `tool_search` | codex |
|
|
46
51
|
*
|
|
47
|
-
* Order matters: cursor → opencode → omp → kimi → zcode → codex — the
|
|
48
|
-
* sharpest Task-based split is `subagent_type` (Cursor) vs `
|
|
49
|
-
* (OpenCode) vs `agent`/`tasks[]` (omp)
|
|
50
|
-
*
|
|
52
|
+
* Order matters: cursor → opencode → omp → dsh → kimi → zcode → codex — the
|
|
53
|
+
* sharpest Task-based split is `subagent_type` (Cursor) vs `task_subagent`
|
|
54
|
+
* (OpenCode) vs `agent`/`tasks[]` (omp); dsh's `subagent` tool (roadmap §4
|
|
55
|
+
* D5) collides with no other row, so it sits with the agent-tool hosts.
|
|
56
|
+
* (`mstar-host` skill prose still says colloquial `subagent` for OpenCode —
|
|
57
|
+
* disambiguate it to `task_subagent` in the same upstream PR that carries
|
|
58
|
+
* this row, together with the CLI `HOST_SIGNALS` list.)
|
|
59
|
+
* Still ambiguous → `"ambiguous"` (prompt judgment stays in the skill).
|
|
51
60
|
*/
|
|
52
61
|
export declare function detectHost(signals: readonly ToolSignal[]): DetectResult;
|
|
53
62
|
/** Skill name + optional skill-relative path for skill-root resolution. */
|
|
@@ -69,7 +78,8 @@ export type SkillRootPaths = {
|
|
|
69
78
|
* | codex | `skills/<name>[/<rel>]` (plugin-mounted; project command skills under `.agents/skills/<name>/`) |
|
|
70
79
|
* | opencode | `harness-skills/<name>[/<rel>]` (package-internal via `@mstar-harness/opencode` — never `process.cwd()/skills/`) |
|
|
71
80
|
* | kimi / zcode | `./skills/<name>[/<rel>]` (plugin mount from the installed plugin root) |
|
|
72
|
-
* |
|
|
81
|
+
* | dsh | `$DSH_BUNDLED_SKILL_DIR/<name>[/<rel>]` (skill-local bundled root — dsh-skill-local `bundledSkillDir` default; the single canonical mount per roadmap D6. Frozen this iteration; local dev mounts the mirror `skills/` via `customSkillDirs` instead, but the canonical published form stays the bundled root) |
|
|
82
|
+
* | pi | deferred — no plugin API in v1 (roadmap §8.4) |
|
|
73
83
|
*/
|
|
74
84
|
export declare function resolveSkillRoot(host: HostId, paths: SkillRootPaths): string;
|
|
75
85
|
/**
|
|
@@ -79,8 +89,8 @@ export declare function resolveSkillRoot(host: HostId, paths: SkillRootPaths): s
|
|
|
79
89
|
* rule: skill text remains authoritative; the engine only returns results
|
|
80
90
|
* the caller chooses to honor). `log` is required — adapters must be able to
|
|
81
91
|
* report. No concrete adapters ship in the engine: opencode binds via its
|
|
82
|
-
* own plugin code, omp via the command layer
|
|
83
|
-
* until
|
|
92
|
+
* own plugin code, omp via the command layer, dsh via the dsh plugin (this
|
|
93
|
+
* iteration); pi stays deferred until its plugin API lands.
|
|
84
94
|
*/
|
|
85
95
|
export interface HostAdapter {
|
|
86
96
|
host: "opencode" | "omp" | "pi" | "dsh" | "cursor" | "codex" | "kimi" | "zcode";
|
package/dist/path.d.ts
CHANGED
|
@@ -10,15 +10,32 @@ export type ResolveHarnessDirOptions = {
|
|
|
10
10
|
* (the caller may scaffold it).
|
|
11
11
|
*/
|
|
12
12
|
harnessDir?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Workspace-root stop boundary (roadmap §7c / plan
|
|
15
|
+
* 20260810-harness-root-boundary). The upward probe keeps walking only
|
|
16
|
+
* while `dir` is at or below this root — a harness dir above it is never
|
|
17
|
+
* returned (the `~/.mstar` global-collision defect is the special case).
|
|
18
|
+
* Resolved against `startDir` when relative. When omitted, the default
|
|
19
|
+
* boundary is the git top-level of `startDir` (sync `git rev-parse
|
|
20
|
+
* --show-cdup`; on failure / non-git start it falls back to
|
|
21
|
+
* `startDir` itself — a non-git start probes only itself, never upward;
|
|
22
|
+
* deliberate tightening). The boundary is an explicit caller value: the
|
|
23
|
+
* engine git-probes only for this default resolution, never during the
|
|
24
|
+
* walk.
|
|
25
|
+
*/
|
|
26
|
+
workspaceRoot?: string;
|
|
13
27
|
};
|
|
14
28
|
/**
|
|
15
29
|
* Resolve `{HARNESS_DIR}` per plan-conventions § {HARNESS_DIR} 解析顺序
|
|
16
30
|
* (find-first-stop): `.mstar/` → `.agents/` → `.plans/`/`plans/`, walking up
|
|
17
|
-
* from `startDir
|
|
18
|
-
*
|
|
19
|
-
*
|
|
31
|
+
* from `startDir` but NEVER above the workspace root (`opts.workspaceRoot`,
|
|
32
|
+
* default = git top-level of `startDir`). Harness candidates are
|
|
33
|
+
* dir-existence (the empty-dir rule applies to `{SPECS_DIR}` only). An
|
|
34
|
+
* explicit override via `opts.harnessDir` or `MSTAR_HARNESS_DIR` wins over
|
|
35
|
+
* probing and short-circuits before any boundary logic.
|
|
20
36
|
*
|
|
21
|
-
* Returns the absolute harness dir, or `null` when no candidate exists
|
|
37
|
+
* Returns the absolute harness dir, or `null` when no candidate exists
|
|
38
|
+
* within the workspace boundary.
|
|
22
39
|
*/
|
|
23
40
|
export declare function resolveHarnessDir(startDir?: string, opts?: ResolveHarnessDirOptions): string | null;
|
|
24
41
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mstar-harness/engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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": {
|