@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.4
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/doflow.js +2 -2
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/registry/storage.json +87 -83
- package/core/registry/verification.json +1 -1
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
- package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
- package/package.json +4 -2
- package/src/adapters/antigravity/{index.ts → index.js} +60 -60
- package/src/adapters/claude/{index.ts → index.js} +48 -48
- package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
- package/src/adapters/codex/{agents.ts → agents.js} +24 -24
- package/src/adapters/codex/{config.ts → config.js} +49 -49
- package/src/adapters/codex/{hooks.ts → hooks.js} +23 -23
- package/src/adapters/codex/{index.ts → index.js} +98 -98
- package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
- package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
- package/src/adapters/copilot/{index.ts → index.js} +42 -42
- package/src/adapters/{copy-tree.ts → copy-tree.js} +70 -70
- package/src/adapters/gemini/{hooks.ts → hooks.js} +24 -24
- package/src/adapters/gemini/{index.ts → index.js} +41 -41
- package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
- package/src/adapters/{index.ts → index.js} +13 -13
- package/src/adapters/kiro/{index.ts → index.js} +34 -34
- package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
- package/src/adapters/opencode/{index.ts → index.js} +52 -52
- package/src/adapters/pi/{index.ts → index.js} +30 -30
- package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
- package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
- package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
- package/src/cli/commands/{install.ts → install.js} +24 -24
- package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
- package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
- package/src/cli/commands/{remove.ts → remove.js} +10 -10
- package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
- package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
- package/src/cli/commands/{status.ts → status.js} +24 -24
- package/src/cli/commands/{tools.ts → tools.js} +16 -16
- package/src/cli/commands/{update.ts → update.js} +19 -19
- package/src/cli/{index.ts → index.js} +26 -26
- package/src/cli/{runtime-commands.ts → runtime-commands.js} +48 -48
- package/src/cli/{shared.ts → shared.js} +40 -40
- package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
- package/src/helper/{git.ts → git.js} +2 -2
- package/src/helper/{guardrails.ts → guardrails.js} +10 -10
- package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
- package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
- package/src/helper/{prompt.ts → prompt.js} +5 -5
- package/src/helper/{repo-root.ts → repo-root.js} +2 -2
- package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
- package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
- package/src/helper/{toml.ts → toml.js} +48 -48
- package/src/install/{backup-set.ts → backup-set.js} +16 -16
- package/src/install/{backup.ts → backup.js} +65 -65
- package/src/install/{context.ts → context.js} +15 -15
- package/src/install/{manifest.ts → manifest.js} +27 -27
- package/src/install/{mcp.ts → mcp.js} +30 -30
- package/src/install/{paths.ts → paths.js} +15 -15
- package/src/install/{targets.ts → targets.js} +10 -10
- package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
- package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
- package/src/lifecycle/{index.ts → index.js} +140 -140
- package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
- package/src/lifecycle/{policies.ts → policies.js} +11 -11
- package/src/lifecycle/{view.ts → view.js} +28 -28
- package/src/registry/{index.ts → index.js} +60 -60
- package/src/release/build-package.js +91 -0
- package/src/release/changelog-section.js +70 -0
- package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
- package/src/runtime/advice/{cli.ts → cli.js} +31 -31
- package/src/runtime/advice/{client.ts → client.js} +44 -44
- package/src/runtime/advice/{config.ts → config.js} +16 -16
- package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
- package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
- package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
- package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
- package/src/runtime/advice/{record.ts → record.js} +19 -19
- package/src/runtime/advice/{registry.ts → registry.js} +16 -16
- package/src/runtime/advice/{strength.ts → strength.js} +7 -7
- package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
- package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
- package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
- package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
- package/src/runtime/{claims.ts → claims.js} +30 -30
- package/src/runtime/{cli-result.ts → cli-result.js} +4 -4
- package/src/runtime/{cli.ts → cli.js} +37 -37
- package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
- package/src/runtime/{context-pack.ts → context-pack.js} +20 -20
- package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
- package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +25 -25
- package/src/runtime/failure/{capture.ts → capture.js} +29 -29
- package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
- package/src/runtime/failure/{cli.ts → cli.js} +30 -30
- package/src/runtime/failure/{home.ts → home.js} +6 -6
- package/src/runtime/failure/{store.ts → store.js} +32 -32
- package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
- package/src/runtime/{freshness.ts → freshness.js} +15 -15
- package/src/runtime/{health.ts → health.js} +72 -72
- package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
- package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
- package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
- package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
- package/src/runtime/inventory/{index.ts → index.js} +36 -36
- package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
- package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
- package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
- package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
- package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
- package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
- package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
- package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
- package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
- package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
- package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
- package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
- package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
- package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
- package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
- package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
- package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
- package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
- package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
- package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
- package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
- package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
- package/src/runtime/{mask.ts → mask.js} +22 -22
- package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
- package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
- package/src/runtime/{model-router.ts → model-router.js} +20 -20
- package/src/runtime/{outcome.ts → outcome.js} +27 -27
- package/src/runtime/{reach.ts → reach.js} +21 -21
- package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
- package/src/runtime/{readiness.ts → readiness.js} +35 -35
- package/src/runtime/{recovery.ts → recovery.js} +15 -15
- package/src/runtime/{research-request.ts → research-request.js} +34 -34
- package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +40 -40
- package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +19 -19
- package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
- package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
- package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
- package/src/runtime/storage/{cli.ts → cli.js} +9 -9
- package/src/runtime/storage/{engine.ts → engine.js} +37 -37
- package/src/runtime/storage/{registry.ts → registry.js} +19 -19
- package/src/runtime/storage/{roots.ts → roots.js} +8 -8
- package/src/runtime/storage/{rules.ts → rules.js} +26 -26
- package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
- package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
- package/src/runtime/{task-attempts.ts → task-attempts.js} +61 -61
- package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
- package/src/runtime/{task-scope.ts → task-scope.js} +13 -13
- package/src/runtime/{task-state.ts → task-state.js} +15 -15
- package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
- package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
- package/src/runtime/trace/{render.ts → render.js} +7 -7
- package/src/runtime/trace/{views.ts → views.js} +40 -40
- package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
- package/src/runtime/verification/{engine.ts → engine.js} +143 -143
- package/src/runtime/verification/{registry.ts → registry.js} +6 -6
- package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
- package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
- package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
- package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
- package/src/runtime/{worktree.ts → worktree.js} +31 -31
- package/src/state/{index.ts → index.js} +26 -26
- package/src/state/{lockfile.ts → lockfile.js} +20 -20
- package/src/state/{run-lock.ts → run-lock.js} +44 -44
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
5
5
|
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
6
|
-
// caller's `require('../helper/prompt.
|
|
6
|
+
// caller's `require('../helper/prompt.js')` keeps working while the rest of the tree is still `.js`.
|
|
7
7
|
const fs = require('node:fs');
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -15,12 +15,12 @@ const fs = require('node:fs');
|
|
|
15
15
|
* done when stdin is actually a TTY (a human might still type) and bounded by a deadline (a fd
|
|
16
16
|
* that will genuinely never produce data must still fail closed, not hang forever).
|
|
17
17
|
*/
|
|
18
|
-
function readSyncBlocking(fd
|
|
18
|
+
function readSyncBlocking(fd , buf ) {
|
|
19
19
|
const deadline = Date.now() + 5 * 60 * 1000;
|
|
20
20
|
for (;;) {
|
|
21
21
|
try {
|
|
22
22
|
return fs.readSync(fd, buf, 0, buf.length, null);
|
|
23
|
-
} catch (e
|
|
23
|
+
} catch (e ) {
|
|
24
24
|
if (!process.stdin.isTTY || e.code !== 'EAGAIN' || Date.now() > deadline) throw e;
|
|
25
25
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
26
26
|
}
|
|
@@ -28,7 +28,7 @@ function readSyncBlocking(fd: number, buf: Buffer): number {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/** true if confirmed (via --force or an explicit y/Y reply) */
|
|
31
|
-
function confirm(message
|
|
31
|
+
function confirm(message , force ) {
|
|
32
32
|
if (force) {
|
|
33
33
|
console.error(`[INFO] ${message} [auto-confirmed via --force]`);
|
|
34
34
|
return true;
|
|
@@ -46,7 +46,7 @@ function confirm(message: string, force: boolean): boolean {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/** Free-text stdin read (e.g. "which backup id?"), matching sync.sh's `read -r reply`. */
|
|
49
|
-
function promptLine(message
|
|
49
|
+
function promptLine(message ) {
|
|
50
50
|
process.stdout.write(message);
|
|
51
51
|
const buf = Buffer.alloc(1024);
|
|
52
52
|
let bytesRead = 0;
|
|
@@ -21,9 +21,9 @@ const path = require('node:path');
|
|
|
21
21
|
*
|
|
22
22
|
* TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
23
23
|
* native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
24
|
-
* caller's `require('../helper/repo-root.
|
|
24
|
+
* caller's `require('../helper/repo-root.js')` keeps working while the rest of the tree is still
|
|
25
25
|
* `.js`. As the highest-fan-out helper, this is the last T1 leaf converted.
|
|
26
26
|
*/
|
|
27
|
-
const REPO_ROOT
|
|
27
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
28
28
|
|
|
29
29
|
module.exports = { REPO_ROOT };
|
|
@@ -15,36 +15,36 @@
|
|
|
15
15
|
//
|
|
16
16
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
17
17
|
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
18
|
-
// caller's `require('../helper/settings-merge.
|
|
18
|
+
// caller's `require('../helper/settings-merge.js')` keeps working while the tree is still `.js`.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
23
|
|
|
24
24
|
/** A hook entry's identity is its (matcher, commands) pair — NOT the commands alone. One script is
|
|
25
25
|
* legitimately registered under several matchers for the same event (post-edit-lint.sh runs on
|
|
26
26
|
* both `Edit` and `Write`), so comparing commands only makes those entries indistinguishable and
|
|
27
27
|
* silently collapses them to one during a merge. Position is not usable: it is not stable across
|
|
28
28
|
* a re-render. */
|
|
29
|
-
function hookCommands(entry
|
|
30
|
-
return (entry?.hooks || []).map((hook) => hook?.command).filter((command)
|
|
29
|
+
function hookCommands(entry ) {
|
|
30
|
+
return (entry?.hooks || []).map((hook) => hook?.command).filter((command) => typeof command === 'string');
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function sameHookEntry(a
|
|
33
|
+
function sameHookEntry(a , b ) {
|
|
34
34
|
if ((a?.matcher ?? null) !== (b?.matcher ?? null)) return false;
|
|
35
35
|
const left = hookCommands(a);
|
|
36
36
|
const right = hookCommands(b);
|
|
37
37
|
return left.length === right.length && left.every((command, index) => command === right[index]);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function isPlainObject(value
|
|
40
|
+
function isPlainObject(value ) {
|
|
41
41
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/** Union of managed hook entries into whatever is already registered for an event. Foreign
|
|
45
45
|
* entries keep their relative order and stay first, so a third-party guard that was already
|
|
46
46
|
* intercepting a tool keeps intercepting it. */
|
|
47
|
-
function mergeHookEvent(existing
|
|
47
|
+
function mergeHookEvent(existing , managed ) {
|
|
48
48
|
const result = Array.isArray(existing) ? existing.slice() : [];
|
|
49
49
|
for (const entry of managed || []) {
|
|
50
50
|
if (!result.some((present) => sameHookEntry(present, entry))) result.push(entry);
|
|
@@ -52,9 +52,9 @@ function mergeHookEvent(existing: HookEntry[] | undefined, managed: HookEntry[]
|
|
|
52
52
|
return result;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function mergeHooks(existing
|
|
55
|
+
function mergeHooks(existing , managed ) {
|
|
56
56
|
const result = isPlainObject(existing) ? { ...existing } : {};
|
|
57
|
-
for (const [event, entries] of Object.entries
|
|
57
|
+
for (const [event, entries] of Object.entries (managed || {})) {
|
|
58
58
|
result[event] = mergeHookEvent(result[event], entries);
|
|
59
59
|
}
|
|
60
60
|
return result;
|
|
@@ -70,10 +70,10 @@ function mergeHooks(existing: any, managed: any): Record<string, HookEntry[]> {
|
|
|
70
70
|
* install is exactly the surprise this module exists to prevent. Managed keys the file does
|
|
71
71
|
* not have yet are still added, so a fresh install is unchanged.
|
|
72
72
|
*/
|
|
73
|
-
function mergeSettings(existing
|
|
73
|
+
function mergeSettings(existing , managed ) {
|
|
74
74
|
if (!isPlainObject(existing)) return managed;
|
|
75
|
-
const result
|
|
76
|
-
for (const [key, value] of Object.entries
|
|
75
|
+
const result = { ...existing };
|
|
76
|
+
for (const [key, value] of Object.entries (managed || {})) {
|
|
77
77
|
if (key === 'hooks') { result.hooks = mergeHooks(existing.hooks, value); continue; }
|
|
78
78
|
if (!Object.hasOwn(result, key)) { result[key] = value; continue; }
|
|
79
79
|
if (isPlainObject(result[key]) && isPlainObject(value)) result[key] = mergeSettings(result[key], value);
|
|
@@ -85,15 +85,15 @@ function mergeSettings(existing: any, managed: any): Settings {
|
|
|
85
85
|
/** Verification predicate replacing byte-equality: DoFlow's own keys must be present, and every
|
|
86
86
|
* managed hook entry must still be registered. A foreign key, a user-tuned `model`, or an extra
|
|
87
87
|
* hook entry are all legitimate and must NOT read as drift. */
|
|
88
|
-
function settingsContains(current
|
|
88
|
+
function settingsContains(current , managed ) {
|
|
89
89
|
if (!isPlainObject(current)) return false;
|
|
90
|
-
for (const [key, value] of Object.entries
|
|
90
|
+
for (const [key, value] of Object.entries (managed || {})) {
|
|
91
91
|
if (key === 'hooks') {
|
|
92
|
-
for (const [event, entries] of Object.entries
|
|
92
|
+
for (const [event, entries] of Object.entries (value || {})) {
|
|
93
93
|
const present = current.hooks?.[event];
|
|
94
94
|
if (!Array.isArray(present)) return false;
|
|
95
95
|
for (const entry of entries || []) {
|
|
96
|
-
if (!present.some((candidate
|
|
96
|
+
if (!present.some((candidate ) => sameHookEntry(candidate, entry))) return false;
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
continue;
|
|
@@ -108,15 +108,15 @@ function settingsContains(current: any, managed: any): boolean {
|
|
|
108
108
|
* rmSync'd the whole file, which on a shared settings.json would delete the user's entire
|
|
109
109
|
* own configuration along with DoFlow's contribution. Returns null only when nothing
|
|
110
110
|
* meaningful remains, letting the caller delete a file DoFlow genuinely created. */
|
|
111
|
-
function stripManagedSettings(current
|
|
111
|
+
function stripManagedSettings(current , managed ) {
|
|
112
112
|
if (!isPlainObject(current)) return null;
|
|
113
|
-
const result
|
|
113
|
+
const result = { ...current };
|
|
114
114
|
const managedHooks = managed?.hooks;
|
|
115
115
|
if (isPlainObject(result.hooks) && isPlainObject(managedHooks)) {
|
|
116
|
-
const hooks
|
|
117
|
-
for (const [event, entries] of Object.entries
|
|
116
|
+
const hooks = {};
|
|
117
|
+
for (const [event, entries] of Object.entries (result.hooks)) {
|
|
118
118
|
const remaining = (entries || []).filter(
|
|
119
|
-
(entry
|
|
119
|
+
(entry ) => !(managedHooks[event] || []).some((owned ) => sameHookEntry(owned, entry)),
|
|
120
120
|
);
|
|
121
121
|
if (remaining.length) hooks[event] = remaining;
|
|
122
122
|
}
|
|
@@ -126,7 +126,7 @@ function stripManagedSettings(current: any, managed: any): Settings | null {
|
|
|
126
126
|
// nothing has touched it since. A key the user changed no longer matches and is left alone.
|
|
127
127
|
// Without this, a file DoFlow created outright (keybindings.json, which has no hooks at all)
|
|
128
128
|
// would survive `remove` fully intact and read as a resource that refused to go away.
|
|
129
|
-
for (const [key, value] of Object.entries
|
|
129
|
+
for (const [key, value] of Object.entries (managed || {})) {
|
|
130
130
|
if (key === 'hooks') continue;
|
|
131
131
|
if (Object.hasOwn(result, key) && JSON.stringify(result[key]) === JSON.stringify(value)) delete result[key];
|
|
132
132
|
}
|
|
@@ -138,12 +138,12 @@ function stripManagedSettings(current: any, managed: any): Settings | null {
|
|
|
138
138
|
* non-hook managed keys are ordinary settings that survive a remove and are indistinguishable
|
|
139
139
|
* from the user's own, so they cannot signal DoFlow's presence. Lets verification tell "removed"
|
|
140
140
|
* (none of ours left) apart from "damaged" (some of ours left, some missing). */
|
|
141
|
-
function settingsContainsAny(current
|
|
141
|
+
function settingsContainsAny(current , managed ) {
|
|
142
142
|
if (!isPlainObject(current)) return false;
|
|
143
|
-
for (const [event, entries] of Object.entries
|
|
143
|
+
for (const [event, entries] of Object.entries (managed?.hooks || {})) {
|
|
144
144
|
const present = current.hooks?.[event];
|
|
145
145
|
if (!Array.isArray(present)) continue;
|
|
146
|
-
if ((entries || []).some((entry
|
|
146
|
+
if ((entries || []).some((entry ) => present.some((candidate ) => sameHookEntry(candidate, entry)))) return true;
|
|
147
147
|
}
|
|
148
148
|
return false;
|
|
149
149
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
// settings-scope.js — GLOBAL_HOOK_PREFIX/PROJECT_HOOK_PREFIX are the shared constants the Claude
|
|
3
|
-
// adapter's settings-asset renderer (src/adapters/claude/index.
|
|
3
|
+
// adapter's settings-asset renderer (src/adapters/claude/index.js#settingsContent) uses to rewrite
|
|
4
4
|
// a project-scoped settings.json's hook `command` paths from the global-only `~/.claude/hooks/`
|
|
5
5
|
// form to Claude Code's documented ${CLAUDE_PROJECT_DIR}/.claude/hooks/ placeholder — see
|
|
6
6
|
// https://code.claude.com/docs/en/hooks. chmodHooksExecutable is a defensive final pass: an
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//
|
|
11
11
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
12
12
|
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
13
|
-
// caller's `require('../helper/settings-scope.
|
|
13
|
+
// caller's `require('../helper/settings-scope.js')` keeps working while the tree is still `.js`.
|
|
14
14
|
const fs = require('node:fs');
|
|
15
15
|
const path = require('node:path');
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ const PROJECT_HOOK_PREFIX = '${CLAUDE_PROJECT_DIR}/.claude/hooks/';
|
|
|
19
19
|
|
|
20
20
|
/** Adds ugo+x (relative — preserves existing bits), not an absolute mode — a hook file copied in
|
|
21
21
|
* at 664/775 must stay that way, only gaining +x. */
|
|
22
|
-
function chmodHooksExecutable(claudeDir
|
|
22
|
+
function chmodHooksExecutable(claudeDir ) {
|
|
23
23
|
const hooksDir = path.join(claudeDir, 'hooks');
|
|
24
24
|
if (!fs.existsSync(hooksDir)) return;
|
|
25
25
|
for (const f of fs.readdirSync(hooksDir)) {
|
|
@@ -8,37 +8,37 @@
|
|
|
8
8
|
//
|
|
9
9
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
10
10
|
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
11
|
-
// caller's `require('../helper/toml.
|
|
11
|
+
// caller's `require('../helper/toml.js')` keeps working while the rest of the tree is still `.js`.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
39
|
|
|
40
|
-
function stripComment(line
|
|
41
|
-
let quote
|
|
40
|
+
function stripComment(line ) {
|
|
41
|
+
let quote = null;
|
|
42
42
|
let escape = false;
|
|
43
43
|
for (let i = 0; i < line.length; i++) {
|
|
44
44
|
const char = line[i];
|
|
@@ -52,8 +52,8 @@ function stripComment(line: string): string {
|
|
|
52
52
|
return line;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function splitAssignment(line
|
|
56
|
-
let quote
|
|
55
|
+
function splitAssignment(line ) {
|
|
56
|
+
let quote = null;
|
|
57
57
|
let escape = false;
|
|
58
58
|
let depth = 0;
|
|
59
59
|
for (let i = 0; i < line.length; i++) {
|
|
@@ -72,8 +72,8 @@ function splitAssignment(line: string): [string, string] | null {
|
|
|
72
72
|
return null;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
function balanced(value
|
|
76
|
-
let quote
|
|
75
|
+
function balanced(value ) {
|
|
76
|
+
let quote = null;
|
|
77
77
|
let escape = false;
|
|
78
78
|
let depth = 0;
|
|
79
79
|
for (const char of value) {
|
|
@@ -91,7 +91,7 @@ function balanced(value: string): boolean {
|
|
|
91
91
|
return !quote && depth === 0;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
function parseValue(raw
|
|
94
|
+
function parseValue(raw ) {
|
|
95
95
|
const value = raw.trim();
|
|
96
96
|
if (!value || !balanced(value)) throw new Error('invalid TOML value');
|
|
97
97
|
if (value === 'true') return true;
|
|
@@ -116,14 +116,14 @@ const BARE_KEY = /^[A-Za-z0-9_-]+$/;
|
|
|
116
116
|
*
|
|
117
117
|
* A dot *inside* a quoted segment is legal TOML (`"gpt-5.5" = 4`) and is preserved — see
|
|
118
118
|
* flattenKeyPath for how it is kept from colliding with a genuine path separator. */
|
|
119
|
-
function parseKeyPath(raw
|
|
120
|
-
const segments
|
|
119
|
+
function parseKeyPath(raw ) {
|
|
120
|
+
const segments = [];
|
|
121
121
|
let i = 0;
|
|
122
122
|
const skipSpace = () => { while (i < raw.length && (raw[i] === ' ' || raw[i] === '\t')) i += 1; };
|
|
123
123
|
skipSpace();
|
|
124
124
|
if (i >= raw.length) return null;
|
|
125
125
|
while (i < raw.length) {
|
|
126
|
-
let segment
|
|
126
|
+
let segment ;
|
|
127
127
|
const quote = raw[i];
|
|
128
128
|
if (quote === '"' || quote === "'") {
|
|
129
129
|
i += 1;
|
|
@@ -158,18 +158,18 @@ function parseKeyPath(raw: string): string[] | null {
|
|
|
158
158
|
* `["a.b"]` and `[a.b]` collide and a surgical edit could rewrite the wrong line. Managed
|
|
159
159
|
* identities (normaliseDesired) are bare-key-only, so their flattened form is unchanged and
|
|
160
160
|
* lookups against them still match. */
|
|
161
|
-
function flattenKeyPath(segments
|
|
161
|
+
function flattenKeyPath(segments ) {
|
|
162
162
|
return segments.map((segment) => segment.replace(/\\/g, '\\\\').replace(/\./g, '\\.')).join('.');
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/** A conservative TOML scanner. It validates the subset needed to safely locate scalar keys;
|
|
166
166
|
* unsupported multi-line syntax fails closed rather than risking a destructive rewrite. `headers`
|
|
167
167
|
* lists each table header line in file order, so a caller never re-detects headers itself. */
|
|
168
|
-
function parseToml(text
|
|
168
|
+
function parseToml(text ) {
|
|
169
169
|
const lines = text.split(/\r?\n/);
|
|
170
|
-
let table
|
|
171
|
-
const entries = new Map
|
|
172
|
-
const headers
|
|
170
|
+
let table = [];
|
|
171
|
+
const entries = new Map ();
|
|
172
|
+
const headers = [];
|
|
173
173
|
for (let index = 0; index < lines.length; index++) {
|
|
174
174
|
const clean = stripComment(lines[index]).trim();
|
|
175
175
|
if (!clean) continue;
|
|
@@ -188,7 +188,7 @@ function parseToml(text: string): ParsedToml {
|
|
|
188
188
|
if (!assignment) throw new Error(`Malformed TOML assignment on line ${index + 1}`);
|
|
189
189
|
const keyPath = parseKeyPath(assignment[0].trim());
|
|
190
190
|
if (!keyPath) throw new Error(`Unsupported TOML key on line ${index + 1}`);
|
|
191
|
-
let value
|
|
191
|
+
let value ;
|
|
192
192
|
try { value = parseValue(assignment[1]); } catch { throw new Error(`Malformed TOML value on line ${index + 1}`); }
|
|
193
193
|
const fullKey = flattenKeyPath([...table, ...keyPath]);
|
|
194
194
|
if (entries.has(fullKey)) throw new Error(`Duplicate TOML key '${fullKey}'`);
|
|
@@ -202,33 +202,33 @@ function parseToml(text: string): ParsedToml {
|
|
|
202
202
|
* keys already defined it; `table-and-value` when one path is both a key's value and a table (by a
|
|
203
203
|
* header, or implied by a longer dotted key). `line` is the 1-based line of the second definition.
|
|
204
204
|
* Throws what parseToml throws. */
|
|
205
|
-
function tableViolations(text
|
|
205
|
+
function tableViolations(text ) {
|
|
206
206
|
parseToml(text);
|
|
207
207
|
const lines = text.split(/\r?\n/);
|
|
208
|
-
const violations
|
|
209
|
-
const opened = new Set
|
|
210
|
-
const dotted = new Set
|
|
211
|
-
const tables = new Set
|
|
212
|
-
const values = new Set
|
|
213
|
-
const asTable = (segments
|
|
208
|
+
const violations = [];
|
|
209
|
+
const opened = new Set ();
|
|
210
|
+
const dotted = new Set ();
|
|
211
|
+
const tables = new Set ();
|
|
212
|
+
const values = new Set ();
|
|
213
|
+
const asTable = (segments , line ) => {
|
|
214
214
|
const flat = flattenKeyPath(segments);
|
|
215
215
|
if (values.has(flat)) violations.push({ kind: 'table-and-value', table: flat, line });
|
|
216
216
|
tables.add(flat);
|
|
217
217
|
};
|
|
218
|
-
let table
|
|
218
|
+
let table = [];
|
|
219
219
|
for (let index = 0; index < lines.length; index++) {
|
|
220
220
|
const clean = stripComment(lines[index]).trim();
|
|
221
221
|
if (!clean) continue;
|
|
222
222
|
const tableMatch = clean.match(/^\[(.+)\]$/);
|
|
223
223
|
if (tableMatch) {
|
|
224
|
-
table = parseKeyPath(tableMatch[1].trim())
|
|
224
|
+
table = parseKeyPath(tableMatch[1].trim()) ;
|
|
225
225
|
const flat = flattenKeyPath(table);
|
|
226
226
|
if (opened.has(flat) || dotted.has(flat)) violations.push({ kind: 'duplicate-table', table: flat, line: index + 1 });
|
|
227
227
|
opened.add(flat);
|
|
228
228
|
for (let length = 1; length <= table.length; length++) asTable(table.slice(0, length), index + 1);
|
|
229
229
|
continue;
|
|
230
230
|
}
|
|
231
|
-
const keyPath = parseKeyPath(splitAssignment(clean)
|
|
231
|
+
const keyPath = parseKeyPath(splitAssignment(clean) [0].trim()) ;
|
|
232
232
|
for (let length = 1; length < keyPath.length; length++) {
|
|
233
233
|
const segments = [...table, ...keyPath.slice(0, length)];
|
|
234
234
|
dotted.add(flattenKeyPath(segments));
|
|
@@ -11,25 +11,25 @@
|
|
|
11
11
|
//
|
|
12
12
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
13
13
|
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
14
|
-
// caller's `require('../install/backup-set.
|
|
14
|
+
// caller's `require('../install/backup-set.js')` keeps working while the tree is still `.js`. The
|
|
15
15
|
// `./backup` require carries `.ts` because backup migrated in the same slice.
|
|
16
16
|
const fs = require('node:fs');
|
|
17
17
|
const path = require('node:path');
|
|
18
|
-
const { BackupError, scopeRelative } = require('./backup.
|
|
18
|
+
const { BackupError, scopeRelative } = require('./backup.js');
|
|
19
19
|
|
|
20
20
|
const REASON_ORDER = ['change', 'companion', 'relocated-from', 'tombstone', 'snapshot', 'record'];
|
|
21
21
|
|
|
22
|
-
function byCodePoint(a
|
|
22
|
+
function byCodePoint(a , b ) {
|
|
23
23
|
if (a === b) return 0;
|
|
24
24
|
return a < b ? -1 : 1;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** What a set entry is on disk now, following links. */
|
|
28
|
-
function statEntry(file
|
|
28
|
+
function statEntry(file , fsImpl ) {
|
|
29
29
|
let stat;
|
|
30
30
|
try {
|
|
31
31
|
stat = fsImpl.statSync(file);
|
|
32
|
-
} catch (err
|
|
32
|
+
} catch (err ) {
|
|
33
33
|
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return { kind: 'absent', size: null, mode: null };
|
|
34
34
|
throw new BackupError(`could not read ${file}: ${err.code || err.message}`);
|
|
35
35
|
}
|
|
@@ -37,19 +37,19 @@ function statEntry(file: string, fsImpl: any): { kind: string; size: number | nu
|
|
|
37
37
|
return { kind: 'other', size: null, mode: null };
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function isAtOrUnder(file
|
|
40
|
+
function isAtOrUnder(file , root ) {
|
|
41
41
|
const resolved = path.resolve(root);
|
|
42
42
|
return file === resolved || file.startsWith(resolved.endsWith(path.sep) ? resolved : resolved + path.sep);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/** Candidates (absolute path -> harnesses and reasons) into a BackupSet, after the exclusions.
|
|
46
46
|
* A path in `records` is kept even under an excluded root. */
|
|
47
|
-
function toBackupSet(candidates
|
|
47
|
+
function toBackupSet(candidates , { scope, scopeRoot, exclude, excluded, records = [], fsImpl } ) {
|
|
48
48
|
const root = path.resolve(scopeRoot);
|
|
49
|
-
const kept = new Set(records.map((file
|
|
49
|
+
const kept = new Set(records.map((file ) => path.resolve(file)));
|
|
50
50
|
const entries = [];
|
|
51
51
|
for (const file of [...candidates.keys()].sort(byCodePoint)) {
|
|
52
|
-
if (!kept.has(file) && exclude.some((x
|
|
52
|
+
if (!kept.has(file) && exclude.some((x ) => isAtOrUnder(file, x))) {
|
|
53
53
|
excluded.push({ path: file, reason: 'doflow-state' });
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
@@ -67,15 +67,15 @@ function toBackupSet(candidates: Map<string, any>, { scope, scopeRoot, exclude,
|
|
|
67
67
|
scope,
|
|
68
68
|
scopeRoot: root,
|
|
69
69
|
entries,
|
|
70
|
-
excluded: excluded.sort((a
|
|
70
|
+
excluded: excluded.sort((a , b ) => byCodePoint(a.path, b.path)),
|
|
71
71
|
count: entries.length,
|
|
72
72
|
existing: files.length,
|
|
73
73
|
absent: entries.filter((e) => e.kind === 'absent').length,
|
|
74
|
-
bytes: files.reduce((sum, e) => sum + (e.size
|
|
74
|
+
bytes: files.reduce((sum, e) => sum + (e.size ), 0),
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function addCandidate(candidates
|
|
78
|
+
function addCandidate(candidates , file , harness , reason ) {
|
|
79
79
|
const key = path.resolve(file);
|
|
80
80
|
if (!candidates.has(key)) candidates.set(key, { harnesses: new Set(), reasons: new Set() });
|
|
81
81
|
const item = candidates.get(key);
|
|
@@ -92,13 +92,13 @@ function addCandidate(candidates: Map<string, any>, file: string, harness: strin
|
|
|
92
92
|
* it writes back (backup.ts mergeRestoredLedger). They join only a set that holds some file already,
|
|
93
93
|
* so a run that backs up nothing still backs up nothing.
|
|
94
94
|
*/
|
|
95
|
-
function backupSetFromPlan({ plan, scope, scopeRoot, exclude = [], records = [], fsImpl = fs }
|
|
95
|
+
function backupSetFromPlan({ plan, scope, scopeRoot, exclude = [], records = [], fsImpl = fs } ) {
|
|
96
96
|
const candidates = new Map();
|
|
97
97
|
const mcpOnly = new Map();
|
|
98
98
|
const resources = plan.ledger?.resources ?? [];
|
|
99
99
|
const tombstones = plan.ledger?.tombstones ?? [];
|
|
100
100
|
// A retained or released change gives up a ledger row and touches no file.
|
|
101
|
-
const changes = plan.changes.filter((change
|
|
101
|
+
const changes = plan.changes.filter((change ) => change.retained !== true && change.release !== true);
|
|
102
102
|
|
|
103
103
|
for (const change of changes) {
|
|
104
104
|
const target = path.resolve(change.target);
|
|
@@ -121,7 +121,7 @@ function backupSetFromPlan({ plan, scope, scopeRoot, exclude = [], records = [],
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
const owned = new Set(resources.filter((r
|
|
124
|
+
const owned = new Set(resources.filter((r ) => typeof r.target === 'string').map((r ) => path.resolve(r.target)));
|
|
125
125
|
for (const tombstone of tombstones) {
|
|
126
126
|
if (tombstone.sweptAt || typeof tombstone.fromTarget !== 'string') continue;
|
|
127
127
|
if (owned.has(path.resolve(tombstone.fromTarget)) || !fsImpl.existsSync(tombstone.fromTarget)) continue;
|
|
@@ -137,7 +137,7 @@ function backupSetFromPlan({ plan, scope, scopeRoot, exclude = [], records = [],
|
|
|
137
137
|
|
|
138
138
|
/** A BackupSet of the given absolute paths, for the pre-rollback snapshot. `records` as for
|
|
139
139
|
* backupSetFromPlan: a restored ledger is snapshotted too, so the snapshot rolls back as a whole. */
|
|
140
|
-
function backupSetFromPaths({ items, scope, scopeRoot, exclude = [], records = [], fsImpl = fs }
|
|
140
|
+
function backupSetFromPaths({ items, scope, scopeRoot, exclude = [], records = [], fsImpl = fs } ) {
|
|
141
141
|
const candidates = new Map();
|
|
142
142
|
for (const item of items) {
|
|
143
143
|
if (item.harnesses.length === 0) addCandidate(candidates, item.path, null, 'snapshot');
|