holistic 0.6.0 → 0.6.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.1 - 2026-04-12
4
+
5
+ Trust & Privacy Hardening (M006). This release implements a "Consent-First" read-only architecture, strengthens privacy boundaries for portable state, and introduces configurable MCP logging and enhanced secret redaction.
6
+
7
+ - Implemented **Read-Only Command Policy**: Routine commands (`status`, `resume`, `diff`, `search`) are now strictly non-mutating. They will surface health warnings for outdated hooks but will never fix them silently.
8
+ - Hardened **Privacy Mode Enforcement**: When `portableState` is disabled (Privacy Mode), generated shell scripts and Git hooks now exit early to prevent any accidental remote state synchronization.
9
+ - Added **MCP Logging Privacy**: Introduced `mcpLogging` configuration (`off` | `minimal` | `default`). Defaults to `minimal` to prevent session objectives and titles from leaking into system logs.
10
+ - Expanded **Secret Redaction**: significantly strengthened the redaction engine to identify and scrub JWT tokens, Bearer tokens, AWS keys, and PEM private key blocks.
11
+ - Added **Redaction Quality Tests**: Integrated 8 new unit tests to verify that sensitive patterns are correctly scrubbed while preserving normal text.
12
+ - Added **SECURITY.md**: Published a comprehensive technical disclosure of Holistic's trust model, data residency guarantees, and safety architecture.
13
+
3
14
  ## 0.6.0 - 2026-04-11
4
15
 
5
16
  Comprehensive Reliability & UX Refinement (M005). This release finalizes the security hardening milestone, introduces granular bootstrap controls, and adds support for explicit portable-state management.
package/README.md CHANGED
@@ -17,35 +17,35 @@ Shared memory for AI agents, built into your repo.
17
17
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
18
18
  [![Node.js](https://img.shields.io/badge/node-%3E%3D24-339933.svg)](./package.json)
19
19
 
20
- ### One command. Every agent. Zero re-explaining. ✨
20
+ ### One command. Every agent. Zero re-explaining. ✨
21
+ No context loss. No fragile handoffs.
21
22
 
22
23
  Holistic gives your AI agents shared memory inside the repo itself. When you switch from Claude to Codex to Gemini, the next agent can see what happened last time, what not to break, and what should happen next.
23
24
 
24
-
25
25
  ---
26
26
 
27
+ ## Why trust Holistic? 🔒
27
28
 
28
- ### Why trust this?
29
+ Holistic is designed to be **safe to install, inspectable, and predictable**.
29
30
 
30
- - 🔐 No known security vulnerabilities
31
- - 🧪 60+ automated tests covering core flows
31
+ - 🔐 **Security-first design** local-first, no telemetry, no external services
32
+ - 🧭 **Consent-first model** system changes only happen via `bootstrap` or `repair`
33
+ - 👀 **Read-only by default** — routine commands never silently modify your repo or machine
34
+ - 🔍 **Fully transparent** — readable scripts, visible hooks, no hidden behavior
35
+ - 🛡️ **Privacy mode by default** — no remote sync unless explicitly enabled
36
+ - 🧪 60+ automated tests covering core flows
32
37
  - 🛠️ Actively maintained (frequent releases)
33
- - 🔍 Transparent local setup (see `SECURITY.md`)
34
-
35
- > Early beta, but built to be safe, inspectable, and predictable.
36
38
 
39
+ > See [SECURITY.md](./SECURITY.md) for full technical details.
37
40
 
38
41
  ---
39
42
 
40
-
41
43
  ## Get started in 30 seconds ⚡
42
44
 
43
45
  Open your project repo in PowerShell, Terminal, Command Prompt, or whatever shell you normally use.
44
46
 
45
47
  Requires Node.js 24+.
46
48
 
47
- Run these two commands:
48
-
49
49
  ```bash
50
50
  npm install -g holistic
51
51
  holistic bootstrap --yes
@@ -61,26 +61,22 @@ That is enough to get the basic Holistic workflow working.
61
61
 
62
62
  If you want the fuller install and setup details, jump to [Quick start](#quick-start-).
63
63
 
64
-
65
64
  ---
66
65
 
67
-
68
66
  ## The problem 😵
69
67
 
70
68
  If you use more than one AI coding assistant, the workflow usually falls apart:
71
69
 
72
- - 🔁 You re-explain the project every session.
73
- - 🐞 Bugs come back because the next agent does not know what was already fixed.
74
- - 🧠 Progress gets lost when context windows end.
75
- - 💥 Agents undo each other because there is no durable handoff.
76
- - 🌫️ It is hard to tell what is actually done.
70
+ - 🔁 You re-explain the project every session
71
+ - 🐞 Bugs come back because the next agent does not know what was already fixed
72
+ - 🧠 Progress gets lost when context windows end
73
+ - 💥 Agents undo each other because there is no durable handoff
74
+ - 🌫️ It is hard to tell what is actually done
77
75
 
78
76
  Holistic fixes that by making the repo the source of truth.
79
77
 
80
-
81
78
  ---
82
79
 
83
-
84
80
  ## What it feels like with HOLISTIC 🌿
85
81
 
86
82
  Run one setup command on a machine:
@@ -91,19 +87,15 @@ holistic bootstrap
91
87
 
92
88
  Then daily use is mostly:
93
89
 
94
- 1. Open the repo in Codex, Claude, or another supported app.
95
- 2. Start a fresh session.
96
- 3. Ask the agent to read `AGENTS.md` and `HOLISTIC.md`.
97
- 4. Let Holistic carry continuity through checkpoints, handoffs, and repo memory.
90
+ 1. Open the repo in Codex, Claude, or another supported app
91
+ 2. Start a fresh session
92
+ 3. Ask the agent to read `AGENTS.md` and `HOLISTIC.md`
93
+ 4. Let Holistic carry continuity through checkpoints, handoffs, and repo memory
98
94
 
99
95
  Most days, you do not need to keep a terminal process open or manually re-brief the agent.
100
96
 
101
- `holistic bootstrap` is a machine setup command, not just a repo setup command. By default it can install local startup helpers and configure Claude Desktop MCP on that machine.
102
-
103
-
104
97
  ---
105
98
 
106
-
107
99
  ## How it works 🧭
108
100
 
109
101
  ```text
@@ -122,9 +114,24 @@ Holistic checkpoints and handoffs keep repo memory current
122
114
  The next agent picks up without a long re-explanation
123
115
  ```
124
116
 
125
-
126
117
  ---
127
118
 
119
+ ## 🔒 Security & Trust Model
120
+
121
+ Holistic is designed to be **transparent, audit-safe, and consent-first**.
122
+
123
+ - **Read-Only by Default** — routine commands warn instead of mutating
124
+ - **Explicit Consent** — system changes require `bootstrap` or `repair`
125
+ - **Granular Control** — apply only what you want (`--yes-*` flags)
126
+ - **Privacy Mode** — sync disabled by default with enforced guards
127
+ - **MCP Logging Controls** — configurable visibility
128
+ - **Redaction** — JWTs, tokens, AWS keys, and PEM blocks scrubbed
129
+ - **Traceable Activity** — logs written locally
130
+ - **Git-native behavior** — respects `.gitignore`
131
+
132
+ For full details, see [SECURITY.md](./SECURITY.md).
133
+
134
+ ---
128
135
 
129
136
  ## Quick start 🚀
130
137
 
@@ -309,19 +316,21 @@ The portable repo memory (config, state, context, sessions) is meant to be commi
309
316
 
310
317
  ## Commands
311
318
 
319
+ | Command | Description |
320
+ | :--- | :--- |
312
321
  | `holistic init` | Base repo setup and scaffolding |
313
322
  | `holistic bootstrap` | One-step machine setup. Required `--yes` for Core Setup, or granular `--yes-*` flags. |
314
- | `holistic doctor` | Runs health checks on machine setup and sync logs |
315
- | `holistic repair` | Regenerates `.holistic/system/` helpers |
316
- | `holistic resume / start --agent <name>` | Loads project recap and prints state |
323
+ | `holistic doctor` | Health checks and configuration diagnostics **(Read-only)** |
324
+ | `holistic repair` | Regenerates `.holistic/system/` local helpers |
325
+ | `holistic resume / start` | Loads project recap and prints state **(Read-only)** |
317
326
  | `holistic start-new` | Starts a fresh session |
318
- | `holistic checkpoint --reason "..."` | Saves progress and context |
319
- | `holistic handoff` | Ends a session with a handoff message |
320
- | `holistic status` | Shows current state and recent sync activity |
321
- | `holistic diff --from <id> --to <id>` | Compares two sessions |
322
- | `holistic search --id <session-id>` | Finds and reactivates an archived session |
323
- | `holistic serve` | Runs the thin MCP server |
324
- | `holistic watch` | Foreground daemon mode for automatic checkpoints |
327
+ | `holistic checkpoint` | Saves progress and context **(Stateful)** |
328
+ | `holistic handoff` | Ends a session with a handoff message **(Stateful)** |
329
+ | `holistic status` | Shows current state and sync activity **(Read-only)** |
330
+ | `holistic diff` | Compares two session IDs **(Read-only)** |
331
+ | `holistic search` | Finds and retrieves session state **(Read-only)** |
332
+ | `holistic serve` | Runs the thin MCP server **(Read-only)** |
333
+ | `holistic watch` | Foreground daemon; automatically creates checkpoints **(Mutating)** |
325
334
 
326
335
  ### Slash command helper text (agent-facing)
327
336
 
@@ -413,18 +422,17 @@ For support and troubleshooting, see [SUPPORT.md](./SUPPORT.md).
413
422
  ---
414
423
 
415
424
 
416
- ## Security, Privacy, and Trust 🔒
417
-
418
425
  Holistic is designed to be **transparent, audit-safe, and consent-first**. It is a shared memory layer that stays in your repo, not a cloud service that watches your screen.
419
426
 
420
427
  ### Trust Architecture:
421
- - **Granular Consent**: `holistic bootstrap` now uses a "Consent-First" model. It displays a summary of system-modifying actions and requires an explicit `--yes` for the Core Setup (hooks, daemon, MCP, attributes).
428
+ - **Read-Only by Default**: Routine commands (`status`, `resume`, `diff`) are strictly non-mutating. They will only **warn** you if git hooks are missing or outdated, rather than fixing them silently.
429
+ - **Granular Consent**: `holistic bootstrap` uses a "Consent-First" model. It displays a summary of system-modifying actions and requires an explicit `--yes` for the Core Setup (hooks, daemon, MCP, attributes).
422
430
  - **Surgical Control**: Use granular flags (`--yes-hooks`, `--yes-daemon`, `--yes-mcp`, `--yes-attr`, `--yes-claude`) to apply only the specific integrations you want.
423
- - **Privacy Mode vs. Portable Mode**: Holistic defaults to **Privacy Mode** (local-only state). To enable cross-device sync, use the `--portable` flag or set `"portableState": true` in the repo config.
424
- - **Read-Only Diagnostics**: The `holistic doctor` command and bootstrap pre-flight checks are strictly non-mutating. They audit your environment without making unexpected changes.
431
+ - **Privacy Mode Enforcement**: When `portableState` is disabled (the default), all generated sync scripts and git hooks contain early-exit guards to prevent any accidental remote traffic.
432
+ - **MCP Logging Privacy**: Control what Holistic reports to your agent UI. Set `mcpLogging` to `"off"`, `"minimal"` (default), or `"default"` in `.holistic/config.json`.
433
+ - **Advanced Redaction**: Holistic automatically scrubs JWTs, Bearer tokens, AWS keys, and PEM blocks from all generated session metadata to prevent context leakage.
425
434
  - **Traceable Activity**: Background sync operations (PowerShell/Bash) are visible and logged with timestamps to `.holistic/system/sync.log`.
426
435
  - **Git-Native Snapshotting**: The repo snapshot logic uses native `git ls-files`, ensuring that your `.gitignore` rules are perfectly respected and performance stays $O(\text{repo size})$.
427
- - **Zero Shell Injection**: Internal commit logic has been stripped of shell wrappers to eliminate command injection risks.
428
436
 
429
437
  ### What it does:
430
438
  - Writes session state into `.holistic/` inside your repo (committed files you control)
@@ -0,0 +1,5 @@
1
+ export declare const tests: {
2
+ name: string;
3
+ run: () => void;
4
+ }[];
5
+ //# sourceMappingURL=privacy-artifacts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy-artifacts.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/privacy-artifacts.test.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,KAAK;;;GA2EjB,CAAC"}
@@ -0,0 +1,75 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { execFileSync } from "node:child_process";
6
+ import { initializeHolistic } from '../core/setup.js';
7
+ import { getRuntimePaths } from '../core/state.js';
8
+ function makeTempRepo() {
9
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "holistic-privacy-test-"));
10
+ execFileSync("git", ["init"], { cwd: root });
11
+ execFileSync("git", ["config", "user.name", "Test User"], { cwd: root });
12
+ execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: root });
13
+ return root;
14
+ }
15
+ export const tests = [
16
+ {
17
+ name: "generated artifacts contain privacy guards when portableState is off",
18
+ run: () => {
19
+ const rootDir = makeTempRepo();
20
+ try {
21
+ initializeHolistic(rootDir, {
22
+ portableState: false,
23
+ installGitHooks: true,
24
+ platform: "win32"
25
+ });
26
+ const paths = getRuntimePaths(rootDir);
27
+ const prePush = fs.readFileSync(path.join(rootDir, ".git", "hooks", "pre-push"), "utf8");
28
+ assert.match(prePush, /exit 0 # Portable state disabled \(Privacy Mode\)/);
29
+ const syncPs1 = fs.readFileSync(path.join(rootDir, ".holistic", "system", "sync-state.ps1"), "utf8");
30
+ assert.match(syncPs1, /Write-Host 'Holistic sync skipped: portableState is disabled \(Privacy Mode\).'/);
31
+ assert.match(syncPs1, /exit 0/);
32
+ const restorePs1 = fs.readFileSync(path.join(rootDir, ".holistic", "system", "restore-state.ps1"), "utf8");
33
+ assert.match(restorePs1, /Write-Host 'Holistic restore skipped: portableState is disabled \(Privacy Mode\).'/);
34
+ assert.match(restorePs1, /exit 0/);
35
+ initializeHolistic(rootDir, {
36
+ portableState: false,
37
+ installGitHooks: true,
38
+ platform: "linux"
39
+ });
40
+ const syncSh = fs.readFileSync(path.join(rootDir, ".holistic", "system", "sync-state.sh"), "utf8");
41
+ assert.match(syncSh, /echo 'Holistic sync skipped: portableState is disabled \(Privacy Mode\).'/);
42
+ assert.match(syncSh, /exit 0/);
43
+ const restoreSh = fs.readFileSync(path.join(rootDir, ".holistic", "system", "restore-state.sh"), "utf8");
44
+ assert.match(restoreSh, /echo 'Holistic restore skipped: portableState is disabled \(Privacy Mode\).'/);
45
+ assert.match(restoreSh, /exit 0/);
46
+ }
47
+ finally {
48
+ fs.rmSync(rootDir, { recursive: true, force: true });
49
+ }
50
+ }
51
+ },
52
+ {
53
+ name: "generated artifacts do NOT contain privacy guards when portableState is on",
54
+ run: () => {
55
+ const rootDir = makeTempRepo();
56
+ try {
57
+ initializeHolistic(rootDir, {
58
+ portableState: true,
59
+ installGitHooks: true,
60
+ platform: "linux"
61
+ });
62
+ const prePush = fs.readFileSync(path.join(rootDir, ".git", "hooks", "pre-push"), "utf8");
63
+ assert.doesNotMatch(prePush, /Privacy Mode/);
64
+ const syncSh = fs.readFileSync(path.join(rootDir, ".holistic", "system", "sync-state.sh"), "utf8");
65
+ assert.doesNotMatch(syncSh, /Privacy Mode/);
66
+ const restoreSh = fs.readFileSync(path.join(rootDir, ".holistic", "system", "restore-state.sh"), "utf8");
67
+ assert.doesNotMatch(restoreSh, /Privacy Mode/);
68
+ }
69
+ finally {
70
+ fs.rmSync(rootDir, { recursive: true, force: true });
71
+ }
72
+ }
73
+ }
74
+ ];
75
+ //# sourceMappingURL=privacy-artifacts.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy-artifacts.test.js","sourceRoot":"","sources":["../../src/__tests__/privacy-artifacts.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare const tests: {
2
+ name: string;
3
+ run: () => void;
4
+ }[];
5
+ //# sourceMappingURL=redact.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/redact.test.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,KAAK;;;GAuFjB,CAAC"}
@@ -0,0 +1,91 @@
1
+ import assert from "node:assert";
2
+ import { sanitizeText } from '../core/redact.js';
3
+ export const tests = [
4
+ {
5
+ name: "sanitizeText redacts OpenAI-style sk- keys",
6
+ run: () => {
7
+ const input = "Use key sk-Abc123Abc123Abc123 for access";
8
+ const output = sanitizeText(input);
9
+ assert.strictEqual(output, "Use key [REDACTED_SECRET] for access");
10
+ },
11
+ },
12
+ {
13
+ name: "sanitizeText redacts GitHub PATs",
14
+ run: () => {
15
+ const input = "My token is ghp_foobarbazqux123";
16
+ const output = sanitizeText(input);
17
+ assert.strictEqual(output, "My token is [REDACTED_SECRET]");
18
+ },
19
+ },
20
+ {
21
+ name: "sanitizeText redacts JWT tokens",
22
+ run: () => {
23
+ const input = "Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoyNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
24
+ const output = sanitizeText(input);
25
+ assert.strictEqual(output, "Authorization: [REDACTED_JWT]");
26
+ },
27
+ },
28
+ {
29
+ name: "sanitizeText redacts Bearer tokens",
30
+ run: () => {
31
+ const input = "Header: Bearer abc.123.def-456";
32
+ const output = sanitizeText(input);
33
+ assert.strictEqual(output, "Header: Bearer [REDACTED]");
34
+ },
35
+ },
36
+ {
37
+ name: "sanitizeText redacts AWS Access Key IDs",
38
+ run: () => {
39
+ const input = "AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE";
40
+ const output = sanitizeText(input);
41
+ assert.strictEqual(output, "AWS_ACCESS_KEY_ID=[REDACTED_AWS_KEY]");
42
+ },
43
+ },
44
+ {
45
+ name: "sanitizeText redacts PEM Private Key blocks",
46
+ run: () => {
47
+ const input = `Here is the key:
48
+ -----BEGIN RSA PRIVATE KEY-----
49
+ MIIEpQIBAAKCAQEA75v...
50
+ ...more...
51
+ -----END RSA PRIVATE KEY-----
52
+ Keep it safe.`;
53
+ const output = sanitizeText(input);
54
+ assert.ok(output.includes("[REDACTED_PEM_PRIVATE_KEY]"));
55
+ assert.ok(!output.includes("MIIEpQIBAAKCAQEA75v"));
56
+ },
57
+ },
58
+ {
59
+ name: "sanitizeText redacts assignment-style secrets",
60
+ run: () => {
61
+ const input = "password: mypassword123; secret=shhh";
62
+ const output = sanitizeText(input);
63
+ assert.strictEqual(output, "password: [REDACTED]; secret= [REDACTED]");
64
+ },
65
+ },
66
+ {
67
+ name: "sanitizeText redacts Azure keys",
68
+ run: () => {
69
+ const input = "azure_key = abc123def456ghi789jkl012mno345pqr";
70
+ const output = sanitizeText(input);
71
+ assert.strictEqual(output, "azure_key = [REDACTED]");
72
+ },
73
+ },
74
+ {
75
+ name: "sanitizeText redacts Stripe keys",
76
+ run: () => {
77
+ const input = "stripe_key: sk_test_51...abc";
78
+ const output = sanitizeText(input);
79
+ assert.strictEqual(output, "stripe_key: [REDACTED]");
80
+ },
81
+ },
82
+ {
83
+ name: "sanitizeText preserves normal text",
84
+ run: () => {
85
+ const input = "The quick brown fox jumps over the lazy dog.";
86
+ const output = sanitizeText(input);
87
+ assert.strictEqual(output, input);
88
+ },
89
+ },
90
+ ];
91
+ //# sourceMappingURL=redact.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.test.js","sourceRoot":"","sources":["../../src/__tests__/redact.test.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAA4C,YAAY,EAAE,aAAa,EAAgB,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA2EvJ,wBAAgB,cAAc,IAAI,MAAM,CA4BvC;AAMD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AA4ED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,CAcnG;AAMD,wBAAgB,UAAU,CAAC,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAoE1G;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CA8D1E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAA4C,YAAY,EAAE,aAAa,EAAgB,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA2EvJ,wBAAgB,cAAc,IAAI,MAAM,CA2BvC;AAMD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAoFD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,CAcnG;AAMD,wBAAgB,UAAU,CAAC,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAoE1G;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CA8D1E"}
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { renderRepoLocalCliCommands } from './core/cli-fallback.js';
7
7
  import { captureRepoSnapshot, clearPendingCommit, commitPendingChanges, writePendingCommit } from './core/git.js';
8
8
  import { writeDerivedDocs } from './core/docs.js';
9
- import { bootstrapHolistic, getSetupStatus, initializeHolistic, refreshHolisticHooks, repairHolistic } from './core/setup.js';
9
+ import { bootstrapHolistic, checkHolisticHooksStatus, getSetupStatus, initializeHolistic, refreshHolisticHooks, repairHolistic, validateRuntimeConfig } from './core/setup.js';
10
10
  import { printSplash, printSplashError, renderSplash } from './core/splash.js';
11
11
  import { requestAutoSync } from './core/sync.js';
12
12
  import { runDaemonTick } from './daemon.js';
@@ -71,30 +71,29 @@ function getVersion() {
71
71
  export function renderHelpText() {
72
72
  return `Holistic CLI v${getVersion()}
73
73
 
74
- Usage:
74
+ Setup Commands:
75
75
  holistic init [--install-daemon] [--install-hooks] [--platform win32|darwin|linux] [--interval 30] [--remote origin] [--state-ref refs/holistic/state] [--state-branch holistic/state]
76
76
  holistic bootstrap [--platform win32|darwin|linux] [--interval 30] [--yes]
77
- holistic doctor
78
77
  holistic repair [--platform win32|darwin|linux] [--refresh-hooks false] [--install-daemon true] [--configure-mcp true]
79
- holistic start [--agent codex|claude|antigravity|gemini|copilot|cursor|goose|gsd|gsd2] [--continue] [--json]
80
- holistic resume [--agent codex|claude|antigravity|gemini|copilot|cursor|goose|gsd|gsd2] [--continue] [--json]
81
- holistic checkpoint --reason "<reason>" [--status "<status>"] [--plan "<step>"]... [--completion-kind natural-breakpoint|task-complete|slice-complete|milestone-complete] [--completion-source agent|system] [--completion-recorded-at <iso8601>]
82
- holistic checkpoint --fixed "<bug>" [--fix-files "<file>"] [--fix-risk "<what reintroduces it>"]
83
- holistic handoff [--draft] [--summary "<summary>"] [--next "<step>"]... [--commit]
84
- holistic start-new [--goal "<goal>"] [--title "<title>"] [--plan "<step>"]...
85
- holistic status
86
- holistic diff --from "<session-id>" --to "<session-id>" [--format text|json]
87
- holistic search --id "<session-id>" [--format text|json]
88
- holistic serve
89
- holistic watch [--agent codex|claude|antigravity|gemini|copilot|cursor|goose|gsd|gsd2] [--interval 60]
90
78
 
91
- Checkpoint examples:
92
- holistic checkpoint --reason "tests passed" --status "Focused verification is green" --completion-kind natural-breakpoint --completion-source agent
93
- holistic checkpoint --reason "feature complete" --status "Ready for handoff" --completion-kind task-complete --completion-source agent
79
+ Read-Only & Diagnostic Commands:
80
+ holistic status | View current repository health and sync status.
81
+ holistic doctor | Run deep diagnostics and configuration validation.
82
+ holistic diff | Compare session state between two points in time.
83
+ holistic search | Find and retrieve session state by ID.
84
+ holistic resume | Load the latest project recap and continue work.
85
+
86
+ Stateful & Mutating Commands:
87
+ holistic checkpoint | Capture current work progress (reason required).
88
+ holistic handoff | Prepare a narrative handoff for the next session.
89
+ holistic start-new | Initialize a new session with a fresh goal.
90
+ holistic watch | Foreground daemon mode; may create checkpoints automatically.
94
91
 
95
- Use checkpoint at natural breakpoints like tests passed, bug fixed, feature complete, focus change, or before compaction. Use handoff as the final safety valve when the session is ending.
92
+ Checkpoint examples:
93
+ holistic checkpoint --reason "tests passed" --status "Focused verification is green"
94
+ holistic checkpoint --reason "feature complete" --status "Ready for handoff"
96
95
 
97
- 'holistic start' is an alias for 'holistic resume'.
96
+ Use checkpoint at natural breakpoints like tests passed, bug fixed, feature complete, or focus change. Use handoff as the final safety valve when the session is ending.
98
97
  `;
99
98
  }
100
99
  function printHelp() {
@@ -108,6 +107,13 @@ function reportHookWarnings(warnings) {
108
107
  process.stderr.write(`${warning}\n`);
109
108
  }
110
109
  }
110
+ function warnIfHooksOutdated(rootDir) {
111
+ const result = checkHolisticHooksStatus(rootDir);
112
+ if (result.refreshed.length > 0) {
113
+ process.stderr.write(`\u26A0 Holistic hooks are outdated. Run 'holistic repair' to refresh them.\n`);
114
+ process.stderr.write(` Hint: Keeping hooks current ensures session continuity and privacy guards are active.\n`);
115
+ }
116
+ }
111
117
  function refreshHooksBeforeCommand(rootDir) {
112
118
  const hookResult = refreshHolisticHooks(rootDir);
113
119
  reportHookWarnings(hookResult.warnings);
@@ -427,13 +433,33 @@ async function handleDoctor(rootDir, parsed) {
427
433
  printSplash({
428
434
  message: "running holistic health check...",
429
435
  });
436
+ const paths = getRuntimePaths(rootDir);
430
437
  const status = getSetupStatus(rootDir);
431
438
  process.stdout.write("\nSystem Configuration:\n\n");
432
439
  printSetupStatusTable(status);
440
+ const findings = validateRuntimeConfig(paths);
441
+ if (findings.length > 0) {
442
+ process.stdout.write("\nConfiguration Diagnostics:\n\n");
443
+ for (const f of findings) {
444
+ const icon = f.level === "error" ? "✖" : f.level === "warn" ? "⚠" : "ℹ";
445
+ process.stdout.write(`${icon} ${f.field.padEnd(20)} | ${f.level.padEnd(10)} | ${f.message}\n`);
446
+ }
447
+ const errors = findings.filter(f => f.level === "error").length;
448
+ const warns = findings.filter(f => f.level === "warn").length;
449
+ if (errors > 0)
450
+ process.stdout.write("\n✖ config invalid\n");
451
+ else if (warns > 0)
452
+ process.stdout.write("\n⚠ config loaded with safe fallbacks\n");
453
+ else
454
+ process.stdout.write("\n✓ config valid\n");
455
+ }
456
+ else {
457
+ process.stdout.write("\n✓ config valid\n");
458
+ }
433
459
  process.stdout.write("\nSync Diagnostics:\n\n");
434
460
  process.stdout.write(renderSyncStatus(rootDir));
435
461
  const healthy = status.every(s => s.status === "ok");
436
- if (healthy) {
462
+ if (healthy && findings.every(f => f.level !== "error")) {
437
463
  process.stdout.write("\n\u2713 Holistic is healthy and correctly configured on this machine.\n");
438
464
  }
439
465
  else {
@@ -489,7 +515,7 @@ Repo-local CLI: ${repairFallback}
489
515
  return 0;
490
516
  }
491
517
  async function handleResume(rootDir, parsed) {
492
- refreshHooksBeforeCommand(rootDir);
518
+ warnIfHooksOutdated(rootDir);
493
519
  const agent = asAgent(firstFlag(parsed.flags, "agent", "unknown"));
494
520
  if (firstFlag(parsed.flags, "continue") === "true") {
495
521
  const mutateResult = mutateState(rootDir, (state) => continueFromLatest(rootDir, state, agent));
@@ -526,7 +552,6 @@ async function handleResume(rootDir, parsed) {
526
552
  return 0;
527
553
  }
528
554
  async function handleCheckpoint(rootDir, parsed) {
529
- refreshHooksBeforeCommand(rootDir);
530
555
  const mutateResult = mutateState(rootDir, (state, paths) => {
531
556
  const regressions = listFlag(parsed.flags, "regression");
532
557
  const fixed = firstFlag(parsed.flags, "fixed");
@@ -577,7 +602,6 @@ async function handleCheckpoint(rootDir, parsed) {
577
602
  return 0;
578
603
  }
579
604
  async function handleStartNew(rootDir, parsed) {
580
- refreshHooksBeforeCommand(rootDir);
581
605
  const agent = asAgent(firstFlag(parsed.flags, "agent", "unknown"));
582
606
  let goal = firstFlag(parsed.flags, "goal");
583
607
  const title = firstFlag(parsed.flags, "title");
@@ -598,7 +622,6 @@ async function handleStartNew(rootDir, parsed) {
598
622
  return 0;
599
623
  }
600
624
  async function handleHandoff(rootDir, parsed) {
601
- refreshHooksBeforeCommand(rootDir);
602
625
  const { state, paths } = loadState(rootDir);
603
626
  if (!state.activeSession) {
604
627
  process.stderr.write("No active session to hand off.\n");
@@ -759,7 +782,7 @@ function renderSyncStatus(rootDir) {
759
782
  return lines.join("\n") + "\n";
760
783
  }
761
784
  async function handleStatus(rootDir) {
762
- refreshHooksBeforeCommand(rootDir);
785
+ warnIfHooksOutdated(rootDir);
763
786
  const { state } = loadState(rootDir);
764
787
  process.stdout.write(renderStatus(rootDir, state));
765
788
  return 0;
@@ -831,7 +854,6 @@ async function handleSearch(rootDir, parsed) {
831
854
  return 0;
832
855
  }
833
856
  async function handleServe(rootDir) {
834
- refreshHooksBeforeCommand(rootDir);
835
857
  printSplashError({
836
858
  message: "starting MCP server on stdio...",
837
859
  });
@@ -863,7 +885,6 @@ async function handleMarkCommit(rootDir, parsed) {
863
885
  return 0;
864
886
  }
865
887
  async function handleWatch(rootDir, parsed) {
866
- refreshHooksBeforeCommand(rootDir);
867
888
  const intervalSeconds = Number.parseInt(firstFlag(parsed.flags, "interval", "60"), 10);
868
889
  const agent = asAgent(firstFlag(parsed.flags, "agent", "unknown"));
869
890
  process.stdout.write(`Watching repo every ${intervalSeconds}s for checkpoint-worthy changes.\n`);