harnessed 4.8.0 → 4.9.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.
@@ -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.0",
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
  }