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.
- package/bin/harnessed-inject-state.mjs +35 -4
- package/dist/cli.mjs +17 -5
- 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
|
@@ -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.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[
|
|
1628
|
+
store.workflows[activeKey()] = s;
|
|
1617
1629
|
await writeStoreRaw(store);
|
|
1618
1630
|
await writeFileAtomic(statePath(), JSON.stringify(s, null, 2));
|
|
1619
1631
|
}
|