harnessed 4.8.0 → 4.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/harnessed-inject-state.mjs +35 -4
- package/dist/cli.mjs +53 -10
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/manifests/optional/perturn-inject.yaml +49 -0
- package/package.json +1 -1
- package/workflows/auto/SKILL.md +12 -12
- package/workflows/auto/SKILL.zh-Hans.md +8 -10
- package/workflows/discuss/auto/SKILL.md +12 -11
- package/workflows/discuss/auto/SKILL.zh-Hans.md +8 -9
- package/workflows/discuss/phase/SKILL.md +12 -11
- package/workflows/discuss/phase/SKILL.zh-Hans.md +8 -9
- package/workflows/discuss/strategic/SKILL.md +12 -11
- package/workflows/discuss/strategic/SKILL.zh-Hans.md +8 -9
- package/workflows/discuss/subtask/SKILL.md +12 -11
- package/workflows/discuss/subtask/SKILL.zh-Hans.md +8 -9
- package/workflows/plan/architecture/SKILL.md +12 -11
- package/workflows/plan/architecture/SKILL.zh-Hans.md +8 -9
- package/workflows/plan/auto/SKILL.md +12 -11
- package/workflows/plan/auto/SKILL.zh-Hans.md +8 -9
- package/workflows/plan/phase/SKILL.md +12 -11
- package/workflows/plan/phase/SKILL.zh-Hans.md +8 -9
- package/workflows/research/SKILL.md +12 -40
- package/workflows/research/SKILL.zh-Hans.md +8 -38
- package/workflows/retro/SKILL.md +12 -11
- package/workflows/retro/SKILL.zh-Hans.md +8 -9
- package/workflows/task/auto/SKILL.md +12 -11
- package/workflows/task/auto/SKILL.zh-Hans.md +9 -10
- package/workflows/task/clarify/SKILL.md +12 -11
- package/workflows/task/clarify/SKILL.zh-Hans.md +9 -10
- package/workflows/task/code/SKILL.md +12 -11
- package/workflows/task/code/SKILL.zh-Hans.md +9 -10
- package/workflows/task/deliver/SKILL.md +12 -11
- package/workflows/task/deliver/SKILL.zh-Hans.md +9 -10
- package/workflows/task/test/SKILL.md +12 -11
- package/workflows/task/test/SKILL.zh-Hans.md +9 -10
- package/workflows/verify/auto/SKILL.md +12 -11
- package/workflows/verify/auto/SKILL.zh-Hans.md +8 -9
- package/workflows/verify/code-review/SKILL.md +12 -11
- package/workflows/verify/code-review/SKILL.zh-Hans.md +8 -9
- package/workflows/verify/design/SKILL.md +12 -11
- package/workflows/verify/design/SKILL.zh-Hans.md +9 -10
- package/workflows/verify/multispec/SKILL.md +12 -11
- package/workflows/verify/multispec/SKILL.zh-Hans.md +9 -10
- package/workflows/verify/paranoid/SKILL.md +12 -11
- package/workflows/verify/paranoid/SKILL.zh-Hans.md +9 -10
- package/workflows/verify/progress/SKILL.md +12 -11
- package/workflows/verify/progress/SKILL.zh-Hans.md +9 -10
- package/workflows/verify/qa/SKILL.md +12 -11
- package/workflows/verify/qa/SKILL.zh-Hans.md +9 -10
- package/workflows/verify/security/SKILL.md +12 -11
- package/workflows/verify/security/SKILL.zh-Hans.md +9 -10
- package/workflows/verify/simplify/SKILL.md +12 -11
- package/workflows/verify/simplify/SKILL.zh-Hans.md +9 -10
|
@@ -38,11 +38,28 @@ function harnessedRoot() {
|
|
|
38
38
|
: join(homedir(), '.claude', 'harnessed')
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
//
|
|
42
|
-
|
|
41
|
+
// Phase 35 — mirror PlatformDescriptor.sessionIdEnv (src/installers/lib/platform.ts)
|
|
42
|
+
// for the hot path: which env var carries the active session id. Minimal replica —
|
|
43
|
+
// HARNESSED_PLATFORM selects (default claude); codex has none. State-root selection
|
|
44
|
+
// (.platform pin / auto-probe) is orthogonal and already handled via
|
|
45
|
+
// HARNESSED_ROOT_OVERRIDE, so it is not re-derived here. The TS `activeKey` uses the
|
|
46
|
+
// full descriptor; the parity test sets matching signals.
|
|
47
|
+
function sessionIdEnvName() {
|
|
48
|
+
const platform = (process.env.HARNESSED_PLATFORM || 'claude').trim()
|
|
49
|
+
if (platform === 'codex') return null
|
|
50
|
+
return 'CLAUDE_CODE_SESSION_ID' // claude (default)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Phase 35 — try the session-scoped slot first, then the bare repoKey slot, then
|
|
54
|
+
// the legacy singleton (dual-write anchor). `keys` is ordered most→least specific.
|
|
55
|
+
function readWorkflow(root, keys) {
|
|
43
56
|
try {
|
|
44
57
|
const store = JSON.parse(readFileSync(join(root, 'workflows.json'), 'utf8'))
|
|
45
|
-
if (store
|
|
58
|
+
if (store?.workflows) {
|
|
59
|
+
for (const k of keys) {
|
|
60
|
+
if (store.workflows[k]) return store.workflows[k]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
46
63
|
} catch {}
|
|
47
64
|
try {
|
|
48
65
|
return JSON.parse(readFileSync(join(root, 'current-workflow.json'), 'utf8'))
|
|
@@ -76,6 +93,14 @@ function workflowStateBlock(wf) {
|
|
|
76
93
|
'RETRO-DUE: enough phases completed since the last retro — run /retro, then `harnessed retro --done`',
|
|
77
94
|
)
|
|
78
95
|
}
|
|
96
|
+
// Phase 36 — scale-adaptive verify_mode directive (parity with injectState.ts).
|
|
97
|
+
if (wf.verify_mode === 'full') {
|
|
98
|
+
lines.push(
|
|
99
|
+
'VERIFY-MODE: full — run full verification (large/risky change: >5 files / >4 subs / >3 reqs)',
|
|
100
|
+
)
|
|
101
|
+
} else if (wf.verify_mode === 'light') {
|
|
102
|
+
lines.push('VERIFY-MODE: light — scope verification to the changed surface (small change)')
|
|
103
|
+
}
|
|
79
104
|
lines.push('</workflow-state>')
|
|
80
105
|
return lines.join('\n')
|
|
81
106
|
}
|
|
@@ -140,8 +165,14 @@ function projectContextBlock(learnings, contextExcerpt) {
|
|
|
140
165
|
|
|
141
166
|
try {
|
|
142
167
|
const root = harnessedRoot()
|
|
168
|
+
// `key` is the repo ROOT directory (used for .planning/ fs paths below). The
|
|
169
|
+
// workflow LOOKUP uses the session-scoped composite key first (Phase 34/35),
|
|
170
|
+
// falling back to the bare repoKey, then the legacy singleton. The composite
|
|
171
|
+
// key is NOT a real directory — only the bare repoKey is.
|
|
143
172
|
const key = repoKey(process.cwd())
|
|
144
|
-
const
|
|
173
|
+
const envName = sessionIdEnvName()
|
|
174
|
+
const sid = envName ? process.env[envName]?.trim() : undefined
|
|
175
|
+
const wf = readWorkflow(root, sid ? [`${key}::${sid}`, key] : [key])
|
|
145
176
|
if (!wf) process.exit(0)
|
|
146
177
|
|
|
147
178
|
const budget = Number(process.env.HARNESSED_INJECT_BUDGET) || DEFAULT_INJECT_BUDGET
|
package/dist/cli.mjs
CHANGED
|
@@ -38,7 +38,7 @@ var init_package = __esm({
|
|
|
38
38
|
"package.json"() {
|
|
39
39
|
package_default = {
|
|
40
40
|
name: "harnessed",
|
|
41
|
-
version: "4.
|
|
41
|
+
version: "4.9.1",
|
|
42
42
|
description: "AI coding harness package manager + composition orchestrator",
|
|
43
43
|
type: "module",
|
|
44
44
|
license: "Apache-2.0",
|
|
@@ -936,7 +936,9 @@ function claudeDescriptor(home = homedir()) {
|
|
|
936
936
|
// the same home base — not a child of homeDir.
|
|
937
937
|
mcpConfigPath: join(home, ".claude.json"),
|
|
938
938
|
// claude writes its env keys into JSON settings.json (capability present).
|
|
939
|
-
supportsEnvKeyWrite: true
|
|
939
|
+
supportsEnvKeyWrite: true,
|
|
940
|
+
// Claude Code exposes the active session id to Bash-invoked CLI + hooks.
|
|
941
|
+
sessionIdEnv: "CLAUDE_CODE_SESSION_ID"
|
|
940
942
|
};
|
|
941
943
|
}
|
|
942
944
|
function codexDescriptor(home = homedir()) {
|
|
@@ -952,7 +954,9 @@ function codexDescriptor(home = homedir()) {
|
|
|
952
954
|
commandsDir: join(codexHome, "prompts"),
|
|
953
955
|
pluginsRegistry: null,
|
|
954
956
|
mcpConfigPath: configToml,
|
|
955
|
-
supportsEnvKeyWrite: false
|
|
957
|
+
supportsEnvKeyWrite: false,
|
|
958
|
+
// No verified codex session-id env → single-session fallback (anti-stale).
|
|
959
|
+
sessionIdEnv: null
|
|
956
960
|
};
|
|
957
961
|
}
|
|
958
962
|
function descriptorById(id, home) {
|
|
@@ -1476,6 +1480,7 @@ var workflowStore_exports = {};
|
|
|
1476
1480
|
__export(workflowStore_exports, {
|
|
1477
1481
|
RetroMetaEntry: () => RetroMetaEntry,
|
|
1478
1482
|
WorkflowStoreV1: () => WorkflowStoreV1,
|
|
1483
|
+
activeKey: () => activeKey,
|
|
1479
1484
|
listWorkflows: () => listWorkflows,
|
|
1480
1485
|
readStoreRaw: () => readStoreRaw,
|
|
1481
1486
|
repoKey: () => repoKey,
|
|
@@ -1500,6 +1505,12 @@ function repoKey(cwd = process.cwd()) {
|
|
|
1500
1505
|
}
|
|
1501
1506
|
return resolve(cwd);
|
|
1502
1507
|
}
|
|
1508
|
+
function activeKey(cwd = process.cwd()) {
|
|
1509
|
+
const base = repoKey(cwd);
|
|
1510
|
+
const envName = detectPlatform().sessionIdEnv;
|
|
1511
|
+
const sid = envName ? process.env[envName]?.trim() : void 0;
|
|
1512
|
+
return sid ? `${base}::${sid}` : base;
|
|
1513
|
+
}
|
|
1503
1514
|
async function readStoreRaw() {
|
|
1504
1515
|
try {
|
|
1505
1516
|
const parsed = JSON.parse(await readFile(storePath(), "utf8"));
|
|
@@ -1539,6 +1550,7 @@ var RetroMetaEntry, WorkflowStoreV1;
|
|
|
1539
1550
|
var init_workflowStore = __esm({
|
|
1540
1551
|
"src/checkpoint/workflowStore.ts"() {
|
|
1541
1552
|
init_harnessedRoot();
|
|
1553
|
+
init_platform();
|
|
1542
1554
|
init_schemaVersion();
|
|
1543
1555
|
init_atomicWrite();
|
|
1544
1556
|
init_currentWorkflow_v1();
|
|
@@ -1605,7 +1617,7 @@ async function withLock(fn) {
|
|
|
1605
1617
|
}
|
|
1606
1618
|
async function readCurrentWorkflow() {
|
|
1607
1619
|
const store = await readStoreRaw();
|
|
1608
|
-
return store.workflows[repoKey()] ?? null;
|
|
1620
|
+
return store.workflows[activeKey()] ?? store.workflows[repoKey()] ?? null;
|
|
1609
1621
|
}
|
|
1610
1622
|
async function writeCurrentWorkflowUnlocked(s) {
|
|
1611
1623
|
if (!Value.Check(CurrentWorkflowV1, s)) {
|
|
@@ -1613,7 +1625,7 @@ async function writeCurrentWorkflowUnlocked(s) {
|
|
|
1613
1625
|
throw new WorkflowStateError(`current-workflow schema validation failed: ${errs}`);
|
|
1614
1626
|
}
|
|
1615
1627
|
const store = await readStoreRaw();
|
|
1616
|
-
store.workflows[
|
|
1628
|
+
store.workflows[activeKey()] = s;
|
|
1617
1629
|
await writeStoreRaw(store);
|
|
1618
1630
|
await writeFileAtomic(statePath(), JSON.stringify(s, null, 2));
|
|
1619
1631
|
}
|
|
@@ -2983,15 +2995,20 @@ var init_masterOrchestrator_helpers = __esm({
|
|
|
2983
2995
|
init_run();
|
|
2984
2996
|
defaultSpawnDriver = async (masterName, subName, _context, packageRoot) => {
|
|
2985
2997
|
const subYamlPath = resolveSubYamlPath(masterName, subName, packageRoot);
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2998
|
+
const result = await runWorkflow(subYamlPath, {}, { packageRoot, gateContext: _context });
|
|
2999
|
+
if (result.status === "failed") {
|
|
3000
|
+
throw new Error(
|
|
3001
|
+
`sub-workflow ${masterName}/${subName} failed` + (result.lastPhaseId ? ` at phase ${result.lastPhaseId}` : "")
|
|
3002
|
+
);
|
|
3003
|
+
}
|
|
3004
|
+
if (result.status === "paused-veto") {
|
|
3005
|
+
throw new Error(
|
|
3006
|
+
`sub-workflow ${masterName}/${subName} paused (governance veto)` + (result.lastPhaseId ? ` at phase ${result.lastPhaseId}` : "")
|
|
2991
3007
|
);
|
|
2992
3008
|
}
|
|
2993
3009
|
};
|
|
2994
3010
|
defaultPauseFn = async (stageName) => {
|
|
3011
|
+
if (!process.stdin.isTTY) return;
|
|
2995
3012
|
const readline2 = await import('readline/promises');
|
|
2996
3013
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
2997
3014
|
try {
|
|
@@ -3014,6 +3031,7 @@ var init_masterOrchestrator_helpers = __esm({
|
|
|
3014
3031
|
return !(a === "n" || a === "no");
|
|
3015
3032
|
};
|
|
3016
3033
|
defaultPrompter = async (question) => {
|
|
3034
|
+
if (!process.stdin.isTTY) return "";
|
|
3017
3035
|
const readline2 = await import('readline/promises');
|
|
3018
3036
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
3019
3037
|
try {
|
|
@@ -3108,6 +3126,15 @@ async function runMasterOrchestrator(masterName, context, packageRoot, spawnDriv
|
|
|
3108
3126
|
for (const r of parallelResults) {
|
|
3109
3127
|
if (r.status === "fulfilled") fired.push(r.value);
|
|
3110
3128
|
}
|
|
3129
|
+
const parallelFailures = parallelResults.map(
|
|
3130
|
+
(r, i) => r.status === "rejected" ? { sub: parallelClauses[i]?.sub, reason: r.reason } : null
|
|
3131
|
+
).filter((x) => x !== null);
|
|
3132
|
+
if (parallelFailures.length > 0) {
|
|
3133
|
+
const detail = parallelFailures.map((f) => `${f.sub}: ${f.reason instanceof Error ? f.reason.message : String(f.reason)}`).join("; ");
|
|
3134
|
+
throw new Error(
|
|
3135
|
+
`[${masterName} master] ${parallelFailures.length} parallel sub(s) failed \u2014 ${detail}`
|
|
3136
|
+
);
|
|
3137
|
+
}
|
|
3111
3138
|
for (const clause of serialTrailing) {
|
|
3112
3139
|
console.log(` \u2192 ${clause.sub} (serial order=${clause.order ?? 0})`);
|
|
3113
3140
|
await spawnDriver(masterName, clause.sub, context, packageRoot);
|
|
@@ -3623,6 +3650,13 @@ function registerRun(program2) {
|
|
|
3623
3650
|
console.log(JSON.stringify({ workflow: name, yamlPath, gateContext }, null, 2));
|
|
3624
3651
|
process.exit(0);
|
|
3625
3652
|
}
|
|
3653
|
+
if (isNestedHarnessContext()) {
|
|
3654
|
+
console.error(
|
|
3655
|
+
`error: \`harnessed run\` is the CI/headless path (in-process SDK spawn) and hangs when invoked from inside a Claude Code session. Use the CC-native \`/${name}\` slash command instead \u2014 it drives subagent spawns via harnessed gates / prompt / checkpoint (keeps the session responsive, enables Agent Teams, allows clarification round-trips). Set HARNESSED_ALLOW_NESTED=1 to override (CI / testing only).`
|
|
3656
|
+
);
|
|
3657
|
+
process.exit(1);
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3626
3660
|
let result;
|
|
3627
3661
|
try {
|
|
3628
3662
|
result = await runWorkflow(yamlPath, {}, { packageRoot: PACKAGE_ROOT, gateContext });
|
|
@@ -3642,6 +3676,14 @@ function registerRun(program2) {
|
|
|
3642
3676
|
process.exit(result.status === "failed" ? 1 : 0);
|
|
3643
3677
|
});
|
|
3644
3678
|
}
|
|
3679
|
+
function isNestedHarnessContext() {
|
|
3680
|
+
if (process.env.HARNESSED_ALLOW_NESTED === "1") return false;
|
|
3681
|
+
const sessEnv = detectPlatform().sessionIdEnv;
|
|
3682
|
+
if (!sessEnv) return false;
|
|
3683
|
+
const sid = process.env[sessEnv]?.trim();
|
|
3684
|
+
if (!sid) return false;
|
|
3685
|
+
return !process.stdin.isTTY;
|
|
3686
|
+
}
|
|
3645
3687
|
async function resolveWorkflowYaml(name, workflowsDir) {
|
|
3646
3688
|
const tier1 = join(workflowsDir, name, "workflow.yaml");
|
|
3647
3689
|
if (existsSync(tier1)) return tier1;
|
|
@@ -3732,6 +3774,7 @@ async function getNextHint(workflowName) {
|
|
|
3732
3774
|
var PACKAGE_ROOT, WORKFLOWS_DIR, _autoChainCache, _autoChainLoadFailed;
|
|
3733
3775
|
var init_run2 = __esm({
|
|
3734
3776
|
"src/cli/run.ts"() {
|
|
3777
|
+
init_platform();
|
|
3735
3778
|
init_path_guard();
|
|
3736
3779
|
init_loadPhases();
|
|
3737
3780
|
init_run();
|