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.
Files changed (55) hide show
  1. package/bin/harnessed-inject-state.mjs +35 -4
  2. package/dist/cli.mjs +53 -10
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/index.mjs.map +1 -1
  6. package/manifests/optional/perturn-inject.yaml +49 -0
  7. package/package.json +1 -1
  8. package/workflows/auto/SKILL.md +12 -12
  9. package/workflows/auto/SKILL.zh-Hans.md +8 -10
  10. package/workflows/discuss/auto/SKILL.md +12 -11
  11. package/workflows/discuss/auto/SKILL.zh-Hans.md +8 -9
  12. package/workflows/discuss/phase/SKILL.md +12 -11
  13. package/workflows/discuss/phase/SKILL.zh-Hans.md +8 -9
  14. package/workflows/discuss/strategic/SKILL.md +12 -11
  15. package/workflows/discuss/strategic/SKILL.zh-Hans.md +8 -9
  16. package/workflows/discuss/subtask/SKILL.md +12 -11
  17. package/workflows/discuss/subtask/SKILL.zh-Hans.md +8 -9
  18. package/workflows/plan/architecture/SKILL.md +12 -11
  19. package/workflows/plan/architecture/SKILL.zh-Hans.md +8 -9
  20. package/workflows/plan/auto/SKILL.md +12 -11
  21. package/workflows/plan/auto/SKILL.zh-Hans.md +8 -9
  22. package/workflows/plan/phase/SKILL.md +12 -11
  23. package/workflows/plan/phase/SKILL.zh-Hans.md +8 -9
  24. package/workflows/research/SKILL.md +12 -40
  25. package/workflows/research/SKILL.zh-Hans.md +8 -38
  26. package/workflows/retro/SKILL.md +12 -11
  27. package/workflows/retro/SKILL.zh-Hans.md +8 -9
  28. package/workflows/task/auto/SKILL.md +12 -11
  29. package/workflows/task/auto/SKILL.zh-Hans.md +9 -10
  30. package/workflows/task/clarify/SKILL.md +12 -11
  31. package/workflows/task/clarify/SKILL.zh-Hans.md +9 -10
  32. package/workflows/task/code/SKILL.md +12 -11
  33. package/workflows/task/code/SKILL.zh-Hans.md +9 -10
  34. package/workflows/task/deliver/SKILL.md +12 -11
  35. package/workflows/task/deliver/SKILL.zh-Hans.md +9 -10
  36. package/workflows/task/test/SKILL.md +12 -11
  37. package/workflows/task/test/SKILL.zh-Hans.md +9 -10
  38. package/workflows/verify/auto/SKILL.md +12 -11
  39. package/workflows/verify/auto/SKILL.zh-Hans.md +8 -9
  40. package/workflows/verify/code-review/SKILL.md +12 -11
  41. package/workflows/verify/code-review/SKILL.zh-Hans.md +8 -9
  42. package/workflows/verify/design/SKILL.md +12 -11
  43. package/workflows/verify/design/SKILL.zh-Hans.md +9 -10
  44. package/workflows/verify/multispec/SKILL.md +12 -11
  45. package/workflows/verify/multispec/SKILL.zh-Hans.md +9 -10
  46. package/workflows/verify/paranoid/SKILL.md +12 -11
  47. package/workflows/verify/paranoid/SKILL.zh-Hans.md +9 -10
  48. package/workflows/verify/progress/SKILL.md +12 -11
  49. package/workflows/verify/progress/SKILL.zh-Hans.md +9 -10
  50. package/workflows/verify/qa/SKILL.md +12 -11
  51. package/workflows/verify/qa/SKILL.zh-Hans.md +9 -10
  52. package/workflows/verify/security/SKILL.md +12 -11
  53. package/workflows/verify/security/SKILL.zh-Hans.md +9 -10
  54. package/workflows/verify/simplify/SKILL.md +12 -11
  55. 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
- // workflows.json[repoKey] first, then the legacy singleton (dual-write anchor).
42
- function readWorkflow(root, key) {
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 && store.workflows && store.workflows[key]) return store.workflows[key]
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 wf = readWorkflow(root, key)
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.8.0",
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[repoKey()] = s;
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
- try {
2987
- await runWorkflow(subYamlPath, {}, { packageRoot, gateContext: _context });
2988
- } catch (err2) {
2989
- console.warn(
2990
- `\u26A0\uFE0F master spawnSubWorkflow Path A failed for ${masterName}/${subName} (${err2.message});Path B sub-shell fallback deferred T3.5.W2.1.`
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();