@njeybe/nativ 2.5.1 → 2.7.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +100 -5
  3. package/dist/commands/design-check.d.ts +4 -0
  4. package/dist/commands/design-check.js +76 -0
  5. package/dist/commands/init.d.ts +1 -0
  6. package/dist/commands/init.js +55 -11
  7. package/dist/commands/notify.d.ts +2 -0
  8. package/dist/commands/notify.js +25 -0
  9. package/dist/commands/session.d.ts +22 -0
  10. package/dist/commands/session.js +106 -0
  11. package/dist/commands/task/task-add.js +1 -1
  12. package/dist/commands/task/task-common.js +6 -6
  13. package/dist/commands/task/task-escalate.js +8 -10
  14. package/dist/commands/task/task-lifecycle.d.ts +7 -1
  15. package/dist/commands/task/task-lifecycle.js +30 -5
  16. package/dist/commands/task/task-query.d.ts +2 -0
  17. package/dist/commands/task/task-query.js +27 -12
  18. package/dist/commands/task/task-reclaim.d.ts +20 -0
  19. package/dist/commands/task/task-reclaim.js +87 -0
  20. package/dist/commands/task.d.ts +2 -0
  21. package/dist/commands/task.js +1 -0
  22. package/dist/commands/triage.js +3 -3
  23. package/dist/commands/update.js +46 -0
  24. package/dist/commands/validate.js +1 -0
  25. package/dist/commands/verify.js +21 -5
  26. package/dist/core/design-rules.d.ts +23 -0
  27. package/dist/core/design-rules.js +115 -0
  28. package/dist/core/enforcement.js +18 -17
  29. package/dist/core/harness-integrity.js +26 -7
  30. package/dist/core/lock-manager.d.ts +6 -0
  31. package/dist/core/lock-manager.js +50 -41
  32. package/dist/core/notify.d.ts +38 -0
  33. package/dist/core/notify.js +133 -0
  34. package/dist/core/profiles.js +7 -16
  35. package/dist/core/session-memory.d.ts +42 -0
  36. package/dist/core/session-memory.js +121 -0
  37. package/dist/core/setup/pm-playbook.d.ts +5 -0
  38. package/dist/core/setup/pm-playbook.js +26 -0
  39. package/dist/core/setup/session-context.js +35 -2
  40. package/dist/core/setup/setup-planner.js +2 -2
  41. package/dist/core/setup/types.d.ts +2 -0
  42. package/dist/core/setup/types.js +5 -1
  43. package/dist/core/task-claims.d.ts +15 -0
  44. package/dist/core/task-claims.js +32 -0
  45. package/dist/core/tier1-liaison.d.ts +1 -0
  46. package/dist/core/tier1-liaison.js +25 -27
  47. package/dist/core/verifier/output-digest.d.ts +19 -0
  48. package/dist/core/verifier/output-digest.js +48 -0
  49. package/dist/db/data-engine.js +33 -32
  50. package/dist/db/introspector.js +61 -60
  51. package/dist/db/sqlite.d.ts +3 -0
  52. package/dist/db/sqlite.js +10 -0
  53. package/dist/governor/circuit-breaker.js +12 -0
  54. package/dist/governor/patch-applier.d.ts +1 -0
  55. package/dist/governor/patch-applier.js +3 -7
  56. package/dist/index.js +40 -6
  57. package/dist/mcp/server.d.ts +1 -1
  58. package/dist/mcp/server.js +53 -14
  59. package/dist/providers/claude-api.js +3 -0
  60. package/dist/providers/gemini.js +4 -1
  61. package/dist/providers/registry.d.ts +6 -2
  62. package/dist/providers/registry.js +28 -16
  63. package/dist/providers/types.d.ts +4 -1
  64. package/dist/runner/agent-supervisor.js +2 -2
  65. package/dist/runner/cli-config.js +1 -1
  66. package/dist/scanner/types.d.ts +5 -1
  67. package/dist/scanner/types.js +7 -0
  68. package/dist/server/routes/task-routes.d.ts +2 -0
  69. package/dist/server/routes/triage-routes.js +3 -18
  70. package/dist/server/studio-ui/pages/benchmarks/styles.js +8 -5
  71. package/dist/server/studio-ui/pages/database/browser.js +1 -1
  72. package/dist/server/studio-ui/pages/database/connection.js +1 -1
  73. package/dist/server/studio-ui/pages/database/data-shell.js +5 -5
  74. package/dist/server/studio-ui/pages/database/drift.js +1 -1
  75. package/dist/server/studio-ui/pages/database/explorer.js +2 -2
  76. package/dist/server/studio-ui/pages/database/styles.js +5 -5
  77. package/dist/server/studio-ui/pages/flow/styles.js +3 -3
  78. package/dist/server/studio-ui/pages/home/script.js +110 -91
  79. package/dist/server/studio-ui/pages/home/styles.js +33 -45
  80. package/dist/server/studio-ui/pages/tasks/script.js +150 -3
  81. package/dist/server/studio-ui/pages/tasks/styles.js +63 -6
  82. package/dist/server/studio-ui/pages/team/script.js +49 -52
  83. package/dist/server/studio-ui/pages/team/styles.js +21 -20
  84. package/dist/server/studio-ui/pages/worktrees/script.js +47 -35
  85. package/dist/server/studio-ui/pages/worktrees/styles.js +12 -10
  86. package/dist/server/studio-ui/shell/brand.d.ts +4 -0
  87. package/dist/server/studio-ui/shell/brand.js +16 -0
  88. package/dist/server/studio-ui/shell/console.js +61 -2
  89. package/dist/server/studio-ui/shell/core.js +8 -8
  90. package/dist/server/studio-ui/shell/detail-view.js +1 -1
  91. package/dist/server/studio-ui/shell/dispatch.js +18 -0
  92. package/dist/server/studio-ui/shell/events-tools.js +5 -1
  93. package/dist/server/studio-ui/shell/format.js +5 -6
  94. package/dist/server/studio-ui/shell/icons.js +23 -15
  95. package/dist/server/studio-ui/shell/markup-head.js +11 -6
  96. package/dist/server/studio-ui/shell/markup-tools.js +19 -10
  97. package/dist/server/studio-ui/shell/paint.js +3 -0
  98. package/dist/server/studio-ui/shell/pipeline-state.js +1 -1
  99. package/dist/server/studio-ui/shell/proposals.js +6 -2
  100. package/dist/server/studio-ui/shell/pulse.d.ts +1 -0
  101. package/dist/server/studio-ui/shell/pulse.js +67 -0
  102. package/dist/server/studio-ui/shell/router.js +25 -0
  103. package/dist/server/studio-ui/shell/sidebar-view.js +6 -3
  104. package/dist/server/studio-ui/shell/toasts-format.js +4 -4
  105. package/dist/server/studio-ui/styles/console.js +33 -13
  106. package/dist/server/studio-ui/styles/dialogs.js +4 -4
  107. package/dist/server/studio-ui/styles/responsive.js +8 -0
  108. package/dist/server/studio-ui/styles/shell.js +24 -5
  109. package/dist/server/studio-ui/styles/strategist.js +10 -10
  110. package/dist/server/studio-ui/styles/tokens-base.js +23 -23
  111. package/dist/server/studio-ui.js +2 -0
  112. package/package.json +2 -2
  113. package/plugin/.claude-plugin/plugin.json +1 -1
  114. package/plugin/agents/architect.md +11 -12
  115. package/plugin/agents/explorer.md +1 -1
  116. package/plugin/agents/verifier.md +2 -2
  117. package/plugin/agents/worker.md +5 -7
  118. package/plugin/skills/nativ/SKILL.md +36 -49
  119. package/templates/AGENTS.md +36 -49
  120. package/templates/CLAUDE.md +1 -41
  121. package/templates/GEMINI.md +3 -2
  122. package/templates/claude-agents/architect.md +11 -12
  123. package/templates/claude-agents/explorer.md +1 -1
  124. package/templates/claude-agents/verifier.md +2 -2
  125. package/templates/claude-agents/worker.md +5 -7
  126. package/templates/design/taste/LICENSE +21 -0
  127. package/templates/design/taste/SOURCE.md +28 -0
  128. package/templates/design/taste/v1/DIGEST.md +57 -0
  129. package/templates/design/taste/v1/SKILL.md +226 -0
  130. package/templates/design/taste/v2/DIGEST.md +62 -0
  131. package/templates/design/taste/v2/SKILL.md +1206 -0
  132. package/templates/dot-ai/api_contracts.json +66 -66
  133. package/templates/dot-ai/context.md +38 -38
  134. package/templates/dot-ai/db_schema.json +39 -39
  135. package/templates/dot-ai/subagents/architect.md +6 -0
  136. package/templates/dot-ai/subagents/flutter-developer.md +2 -11
  137. package/templates/dot-ai/subagents/frontend.md +47 -44
  138. package/templates/dot-ai/ui_specs.md +15 -36
package/CHANGELOG.md CHANGED
@@ -1,5 +1,72 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.7.0
4
+
5
+ ### Added
6
+
7
+ - **Design profiles.** The Design Profile in `.ai/ui_specs.md` picks `taste-v1` or `taste-v2`. Workers follow the chosen profile's rules before they build UI. Native mobile and terminal UI get no profile and follow only their own spec; an unset profile makes frontend workers escalate.
8
+ - **Bundled taste-skill v1 and v2.** init and update install `.ai/design/taste/` with each version's `SKILL.md` and a short `DIGEST.md` that workers read first. Untouched copies refresh on update. Credit: Leonxlnx, MIT License, pinned at commit `717446e`.
9
+ - **`nativ init --design`.** Choose the design profile when scaffolding a project.
10
+ - **`nativ design check`.** Scans UI files with eight rules for emoji, em and en dashes, `h-screen`, scroll listeners, pure black, Google Fonts links, hand-rolled icons and custom cursors. Exits 1 on errors; warnings never fail. Supports `--profile` and `--json`.
11
+ - **Frontend pre-flight.** The frontend guide runs `nativ design check` on the task's UI files before a worker finishes.
12
+ - **Doctor findings.** `nativ doctor` reports the design profile (`specs:design-profile`) and the bundle (`design:taste-bundle`).
13
+
14
+ ### Changed
15
+
16
+ - **Studio redesign.** Studio follows the taste-v1 design profile in a monochrome "Ink" direction. It has light and dark themes, and color is used only for status.
17
+ - **New wordmark.** The nativ wordmark replaces the old letter logo and follows the theme.
18
+ - **Phosphor icons.** Studio icons are now Phosphor (MIT). See Third-party notices in the README.
19
+ - **Pulse strip.** A strip under the top bar shows milestone progress and running, blocked and decision counts on every page.
20
+ - **Dispatch button.** The top bar has a "Dispatch `<next task>`" button.
21
+ - **Inspector.** The console drawer is now a right-side inspector. On narrow screens it is a sheet.
22
+ - **Home is a "now" page.** It shows decisions, running tasks, up next, milestone progress and recent activity.
23
+ - **Tasks list.** Tasks defaults to a list grouped by milestone, with filters and a Board toggle.
24
+ - **Team roster.** Team is a roster table.
25
+ - **Worktrees.** Worktree changes open in the inspector.
26
+ - **Restyled pages.** Flow, Benchmarks, Database and dialogs are restyled.
27
+
28
+ ### Removed
29
+
30
+ - The `nativ` design profile and every nativ-authored design rule. `nativ init --design` and `--profile` accept only `taste-v1` or `taste-v2`; `nativ design check` exits 1 until a profile is set or passed.
31
+ - The Anti-Generic Checklist (Web, Marketing pages, Mobile) from `ui_specs.md`, and the Transitions and Active state lines of its Accessibility section.
32
+ - Frontend guide: Visual style, Anti-generic rules and Build-time details, plus the `nativ` fallback and the style and Marketing pages checks in Before you finish.
33
+ - Flutter guide: Anti-generic rules.
34
+ - Architect guides: the `nativ` profile choice, the Inter typeface rule and the Anti-Generic and Marketing pages checks.
35
+ - Doctor finding `specs:marketing`.
36
+ - `nativ update` does not change an existing project's `.ai/ui_specs.md`, so projects keep their own text. Their frontend workers escalate UI tasks until the architect fills the Design Profile.
37
+
38
+ ## 2.6.0
39
+
40
+ ### Improved: token usage and autonomy
41
+
42
+ - **Leaner directives.** `AGENTS.md` (-17%), `CLAUDE.md` (-20%) and the worker and verifier agents are shorter. Subagents no longer re-read `AGENTS.md`, which already reaches them through `CLAUDE.md`. The human-communication rules now live in one place.
43
+ - **Compact JSON.** `task next`, `task list` and `triage --json` print single-line JSON. `recommendedContractSlice` is a bare file list.
44
+ - **Brief task list over MCP.** `nativ_task_list` returns a short summary per task; pass `full: true` for every field.
45
+ - **Self-healing ladder.** `AGENTS.md` and `CLAUDE.md` tell agents to retry, patch additively, then run `nativ triage --all --apply --json`; only `REQUIRE_HUMAN_DECISION` items, destructive changes, credentials and repeated failures reach the human.
46
+ - **Resumable sessions.** The SessionStart orientation lists blocked tasks and pending escalations.
47
+ - **Budget guard.** `tests/test-token-budget.mjs` fails when an always-loaded file outgrows its token budget.
48
+
49
+ ### Added
50
+
51
+ - **Self-healing guide.** `docs/self-healing.md` explains the hands-free loop end to end: claims, the escalation ladder, reclaiming stuck tasks and notifications.
52
+ - **`nativ_validate` MCP tool.** A read-only contract check for MCP hosts that returns only the problems and a verdict, not every passing line.
53
+ - **Notifications when nativ needs you.** A `notify` webhook (Slack, Discord, ntfy, any JSON endpoint) or command in `.nativ/config.json` is called when triage returns `REQUIRE_HUMAN_DECISION` or the circuit breaker blocks a task, once per escalation. Webhooks must be https. `nativ notify test` sends a sample. Failures are recorded, never fatal.
54
+ - **`nativ_triage` MCP tool.** Hosts that only speak MCP can now run the self-healing step: it evaluates pending escalations (or one), applies sandbox-proven additive fixes, and returns the decision card for items a person must decide. The directives and session orientation point to it.
55
+
56
+ ### Fixed
57
+
58
+ - **Tasks are claimed, not just started.** `task start` refuses a completed or blocked task and one whose dependencies are unfinished, and records `claimedBy` (`--agent` or `NATIV_AGENT_ID`) and `claimedAt`. Another agent cannot start a task with a fresh claim; a claim older than 4 hours can be taken over. Complete and block release the claim. A person can override with `--force`. `nativ_task_start` takes an optional `agent`.
59
+ - **Stuck tasks recover.** `nativ task reclaim` (and `nativ_task_reclaim`) returns `in_progress` tasks with a claim older than 4 hours (`--older-than`) to pending, so a crashed agent no longer leaves a task stuck forever. `--dry-run` reports only. The SessionStart orientation lists stale tasks, and the self-healing ladder covers them.
60
+ - **Provider calls survive network blips.** A network failure or a 5xx answer from Claude API or Gemini is now a `TRANSIENT` error, retried on the same provider twice (about 0.5s and 1s, with jitter) before the chain moves to the next provider. Rejected requests and timeouts are not retried.
61
+ - **Shared JSON files are written safely.** Contract patches, `.nativ/config.json`, task unlocks, provider cooldowns and runner records now go through one atomic writer that retries Windows sharing violations; contract patches no longer fail silently on Windows. Config and unlock changes are read-modify-written under a lock, so parallel writers keep each other's keys, and a config file that does not parse is left untouched instead of being wiped. Studio reports a failed triage-config save instead of claiming success.
62
+ - **Locks wait as long everywhere and leave no empty files.** The plan lock now waits about 5s like the other locks (it gave up after about 0.4s), and locking a file that does not exist no longer creates an empty one that JSON readers cannot parse.
63
+ - **Unknown escalation types are refused.** `nativ task escalate --type` used to turn a typo into `architectural_ambiguity` silently; it now lists the valid types and writes nothing. The CLI and MCP share one list.
64
+ - **`nativ init` writes the architect guide.** It copied a fixed list of eight guides and skipped `architect.md`; it now copies every guide in the templates. `nativ validate` checks the architect guide when present.
65
+ - **Windows paths in `.mcp.json` count as machine-specific on every OS.** A `C:/...` command was treated as portable when setup ran on Linux or macOS.
66
+ - **Continuous integration.** `.github/workflows/ci.yml` builds, checks the plugin sync and runs every suite on Ubuntu (Node 20 and 22) and Windows (Node 22) for pushes to `main` and `dev` and for pull requests. The test runner clears `CI` and the headless overrides for each suite, so tests behave the same on a laptop and in CI.
67
+ - **SQLite on Node 20 explains itself.** SQLite support uses `node:sqlite`, which ships with Node.js 22.5+. On Node 20 the database commands and Studio now say so plainly instead of failing with an unknown-module error. The contract-generator tests no longer depend on `node --test` glob support, which Node 20 lacks.
68
+ - **`npm test` runs every suite.** `tests/run-all.mjs` keeps going after a failure and lists every failed suite at the end.
69
+
3
70
  ## 2.5.1
4
71
 
5
72
  ### Fixed
package/README.md CHANGED
@@ -26,6 +26,7 @@ cd your-project && nativ init # scaffold .ai/ and the Claude Code configurati
26
26
  | | [Database](#database) | [Troubleshooting](#troubleshooting) |
27
27
  | | [Worktrees](#worktrees) | [For maintainers](#for-maintainers) |
28
28
  | | [Studio, tests and benchmarks](#studio-tests-and-benchmarks) | |
29
+ | | | [Design profiles](#design-profiles) |
29
30
 
30
31
  ---
31
32
 
@@ -140,7 +141,8 @@ Every command takes an optional `[targetDir]` (default: the current directory) a
140
141
 
141
142
  | Command | What it does | Who |
142
143
  | :--- | :--- | :--- |
143
- | [`init`](#nativ-init) | Scaffold `.ai/` and the Claude Code configuration | You |
144
+ | [`init`](#nativ-init) | Scaffold `.ai/` and the Claude Code configuration; `--design` picks a design profile | You |
145
+ | [`design check`](#nativ-design-check) | Scan UI files for design-rule violations | You, Agent |
144
146
  | [`setup`](#nativ-setup) | Write or merge the Claude Code configuration | You |
145
147
  | [`profile`](#nativ-profile) | Inspect or switch ceremony profile (`prototype`, `solo`, `enterprise`) | You, Agent |
146
148
  | [`doctor`](#nativ-doctor) | Check the integration and the harness; `--fix` repairs | You, Agent (read-only) |
@@ -152,6 +154,7 @@ Every command takes an optional `[targetDir]` (default: the current directory) a
152
154
  | [`task list`](#nativ-task-list) / `tasks` | List tasks with filters | You, Agent |
153
155
  | [`task add`](#nativ-task-add) | Add a task | Architect |
154
156
  | [`task start`](#nativ-task-start) | Mark a task in progress | Agent |
157
+ | [`task reclaim`](#nativ-task-reclaim) | Free tasks left in progress by a stopped agent | You, Agent |
155
158
  | [`task complete`](#nativ-task-complete) | Complete a task through the gatekeeper | Agent |
156
159
  | [`task block`](#nativ-task-block) | Block a task with a reason | Agent |
157
160
  | [`task escalate`](#nativ-task-escalate) | Escalate a contract gap to the architect | Agent |
@@ -162,6 +165,7 @@ Every command takes an optional `[targetDir]` (default: the current directory) a
162
165
  | [`learn list`](#nativ-learn-list) | List lessons | You, Agent |
163
166
  | [`learn approve` / `reject`](#nativ-learn-approve--reject) | Decide on a lesson | *You only* |
164
167
  | [`triage`](#nativ-triage) | Evaluate pending escalations; decision cards for you | You |
168
+ | [`notify test`](#nativ-notify-test) | Check the webhook or command that tells you a decision is waiting | You |
165
169
  | [`db status` / `inspect` / `diff`](#nativ-db-status--inspect--diff) | Masked, structure-only database checks | You, Agent |
166
170
  | [`db sync`](#nativ-db-sync) | Copy a live schema into `.ai/db_schema.json` | *You only* |
167
171
  | [`worktree create` / `list` / `merge` / `remove`](#nativ-worktree) | Isolated worktrees for parallel tasks | You, Agent |
@@ -188,6 +192,20 @@ nativ init [targetDir] [-f, --force]
188
192
  | Option | Description |
189
193
  | :--- | :--- |
190
194
  | `-f, --force` | Overwrite existing specification and directive files |
195
+ | `--design <taste-v1|taste-v2>` | Set the design profile in `.ai/ui_specs.md` and install the taste-skill bundle |
196
+
197
+ #### `nativ design check`
198
+
199
+ Scan UI source files for design-rule violations. Exits 1 on any error.
200
+
201
+ ```bash
202
+ nativ design check [paths...] [--profile <id>] [--json]
203
+ ```
204
+
205
+ | Option | Description |
206
+ | :--- | :--- |
207
+ | `--profile <id>` | `taste-v1` or `taste-v2`; default is the profile in `.ai/ui_specs.md`. With neither, the command exits 1 |
208
+ | `--json` | Print findings as JSON |
191
209
 
192
210
  #### `nativ setup`
193
211
 
@@ -361,10 +379,18 @@ nativ task add "Appointment list" -a frontend --complexity standard \
361
379
  #### `nativ task start`
362
380
 
363
381
  ```bash
364
- nativ task start <taskId> [targetDir]
382
+ nativ task start <taskId> [targetDir] [--agent <id>] [--force]
383
+ ```
384
+
385
+ Claims the task, marks it `in_progress` and records the baseline the test-integrity guard compares against. The task must be pending and its dependencies completed; a blocked task goes through `nativ triage` first, and only a person can override with `--force`. The claim records `claimedBy` (`--agent` or `$NATIV_AGENT_ID`) and `claimedAt`, so a parallel agent cannot take a task another agent holds. Running `task start` again on your own task refreshes the claim; a claim older than 4 hours counts as abandoned.
386
+
387
+ #### `nativ task reclaim`
388
+
389
+ ```bash
390
+ nativ task reclaim [taskId] [targetDir] [--older-than <hours>] [--dry-run] [--json]
365
391
  ```
366
392
 
367
- Marks the task `in_progress` and records the baseline the test-integrity guard compares against.
393
+ Puts `in_progress` tasks whose agent stopped (a crash or a closed session) back to `pending`. Without a task id it reclaims every claim older than `--older-than` hours (default 4). Tasks started before claims were recorded have no claim time; name them to reclaim them. A fresh claim is refused unless a person passes `--force`. The SessionStart orientation lists stale tasks. The previous agent may have left partial changes, so review the working tree before restarting.
368
394
 
369
395
  #### `nativ task complete`
370
396
 
@@ -517,7 +543,7 @@ If the lesson changed while you were reviewing it, nothing is decided and you ar
517
543
 
518
544
  ### Escalations and triage
519
545
 
520
- A worker that finds a contract gap escalates (`nativ task escalate`) and stops. The gap is then resolved by the architect, or first assessed by triage.
546
+ A worker that finds a contract gap escalates (`nativ task escalate`) and stops. The gap is then resolved by the architect, or first assessed by triage. [docs/self-healing.md](docs/self-healing.md) walks through the whole hands-free loop: claims, retries, triage, reclaiming stuck tasks and notifications.
521
547
 
522
548
  #### `nativ triage`
523
549
 
@@ -536,6 +562,16 @@ nativ triage [escalationId] [targetDir] [options]
536
562
 
537
563
  When an escalation is settled, its resolution notes reach the next worker on that task as `priorEscalations`.
538
564
 
565
+ #### `nativ notify test`
566
+
567
+ nativ can tell you when it needs you: when triage returns `REQUIRE_HUMAN_DECISION`, and when the circuit breaker blocks a task. Each escalation is announced once. Add a webhook (Slack, Discord, ntfy or any JSON endpoint) or a local command to `.nativ/config.json`, which git ignores, so a webhook token stays on your machine:
568
+
569
+ ```json
570
+ { "notify": { "webhook": "https://hooks.slack.com/services/...", "events": ["human_decision", "circuit_breaker_tripped"] } }
571
+ ```
572
+
573
+ The webhook receives JSON with `text` (Slack, ntfy), `content` (Discord), and the task, escalation, question, options and next step as fields. Webhooks must use `https://` (plain `http://` only to `localhost`). A `"command"` receives the same JSON on stdin and in `NATIV_NOTIFY_JSON`, e.g. `"command": "notify-send nativ \"$(jq -r .text)\""`. `nativ notify test` sends a sample; delivery failures never stop the workflow and are recorded in `.nativ/notify-state.json`.
574
+
539
575
  [Back to contents](#contents)
540
576
 
541
577
  ---
@@ -636,6 +672,29 @@ Run the MCP server over stdio. `setup` registers it; see [MCP server](#mcp-serve
636
672
 
637
673
  ---
638
674
 
675
+ ## Design profiles
676
+
677
+ nativ can hold web UI work to a design profile. Pick one per web project; it is recorded in the
678
+ Design Profile section of `.ai/ui_specs.md`.
679
+
680
+ | Profile | Use for |
681
+ | :--- | :--- |
682
+ | `taste-v2` | Landing sites, portfolios, editorial pages and web redesigns |
683
+ | `taste-v1` | Web apps, SaaS screens and dashboards |
684
+
685
+ Native mobile (Flutter) and terminal UI get no profile. They follow only their own spec.
686
+
687
+ Set it with `nativ init --design taste-v2` (or `taste-v1`), or let the architect pick one during
688
+ planning. An unset profile is a spec gap: frontend workers escalate, and `nativ design check`
689
+ exits 1 until a profile is set or passed with `--profile`. An architect must pick one. Init and update install the bundle in `.ai/design/taste/`: the
690
+ LICENSE, SOURCE.md, and `v1/` and `v2/` folders, each with `SKILL.md` and a short `DIGEST.md`
691
+ that workers read first. Run `nativ design check` to scan UI files for rule violations.
692
+
693
+ Credit: the taste-skill prompts are by Leonxlnx, [github.com/Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill),
694
+ MIT License, pinned at commit `717446e`.
695
+
696
+ ---
697
+
639
698
  ## Configuration
640
699
 
641
700
  ### `.nativ/config.json`
@@ -665,12 +724,15 @@ Written by `setup`; every key is optional.
665
724
  | `codeStyle.maxCommentLines` | `2` | Longest allowed comment block |
666
725
  | `providers` | see [Providers](#providers) | Provider order per role: `architect`, `pm`, `worker`, `verifier`, `triage` |
667
726
  | `models` | provider default | Model per provider |
727
+ | `notify` | off | `webhook`, `command` and `events` for human notifications. See [`nativ notify test`](#nativ-notify-test) |
668
728
 
669
729
  ### Environment variables
670
730
 
671
731
  | Variable | Used for |
672
732
  | :--- | :--- |
673
733
  | `NATIV_ROLE` | Session role; `NATIV_ROLE=architect` lets a whole session write `.ai/` |
734
+ | `NATIV_AGENT_ID` | Who claims a task on `task start`, so parallel agents never take the same task |
735
+ | `NATIV_NOTIFY_WEBHOOK`, `NATIV_NOTIFY_COMMAND` | Override the `notify` webhook or command from `.nativ/config.json` |
674
736
  | `ANTHROPIC_API_KEY` | The `claude-api` provider and the Studio native runner |
675
737
  | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | The `gemini` provider |
676
738
  | `NATIV_DEV_DATABASE_URL`, `DEV_DATABASE_URL`, `DATABASE_URL` | Dev database for `nativ db`, checked in that order, read from your `.env*` files. MongoDB (`MONGODB_URI`) and Firestore variants work too, and names declared in `.env.example` are checked first |
@@ -777,7 +839,9 @@ Files nativ wrote carry a marker, so `update` and `doctor` can tell untouched fi
777
839
  | Tools | Purpose |
778
840
  | :--- | :--- |
779
841
  | `nativ_task_next`, `nativ_task_list`, `nativ_task_add` | Find and add work |
780
- | `nativ_task_start`, `nativ_task_complete`, `nativ_task_block` | Move a task through its lifecycle (complete always runs the gatekeeper) |
842
+ | `nativ_task_start`, `nativ_task_complete`, `nativ_task_block`, `nativ_task_reclaim` | Move a task through its lifecycle (complete always runs the gatekeeper; reclaim frees stale claims) |
843
+ | `nativ_validate` | Check the `.ai/` contracts, plan and role guides; returns only problems and a verdict |
844
+ | `nativ_triage` | Self-heal: evaluate pending escalations, apply sandbox-proven safe fixes, return decision cards for the rest |
781
845
  | `nativ_task_escalate`, `nativ_task_propose_patch` | Escalate a gap or propose a governed patch |
782
846
  | `nativ_learn_propose` | Propose a lesson; you approve it in a terminal |
783
847
  | `nativ_verify`, `nativ_status`, `nativ_bench`, `nativ_test_gen` | Verification, progress, benchmarks, test generation |
@@ -882,3 +946,34 @@ npm run sync-plugin # after changing a template or the setup generator; a t
882
946
  ## License
883
947
 
884
948
  MIT (c) JB Natividad. See [LICENSE](LICENSE).
949
+
950
+ ## Third-party notices
951
+
952
+ - **Phosphor Icons** (`@phosphor-icons/core` 2.1.1) is vendored into Studio under the MIT license:
953
+
954
+ ```
955
+ MIT License
956
+
957
+ Copyright (c) 2023 Phosphor Icons
958
+
959
+ Permission is hereby granted, free of charge, to any person obtaining a copy
960
+ of this software and associated documentation files (the "Software"), to deal
961
+ in the Software without restriction, including without limitation the rights
962
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
963
+ copies of the Software, and to permit persons to whom the Software is
964
+ furnished to do so, subject to the following conditions:
965
+
966
+ The above copyright notice and this permission notice shall be included in all
967
+ copies or substantial portions of the Software.
968
+
969
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
970
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
971
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
972
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
973
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
974
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
975
+ SOFTWARE.
976
+ ```
977
+
978
+ - **taste-skill** by Leonxlnx (MIT). The bundled copy keeps its own LICENSE under
979
+ `templates/design/taste/`.
@@ -0,0 +1,4 @@
1
+ export declare function runDesignCheck(paths: string[] | undefined, options: {
2
+ profile?: string;
3
+ json?: boolean;
4
+ }): void;
@@ -0,0 +1,76 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { DESIGN_PROFILES, SCAN_EXTENSIONS, checkText, readProfile, } from '../core/design-rules.js';
4
+ const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', '.next', '.ai']);
5
+ let jsonMode = false;
6
+ function fail(msg) {
7
+ if (jsonMode)
8
+ console.log(JSON.stringify({ error: msg }));
9
+ else
10
+ console.error(msg);
11
+ process.exit(1);
12
+ }
13
+ function isScannable(name) {
14
+ if (/\.(test|spec)\./.test(name))
15
+ return false;
16
+ return SCAN_EXTENSIONS.includes(path.extname(name));
17
+ }
18
+ function collect(target, out) {
19
+ const stat = fs.statSync(target);
20
+ if (stat.isFile()) {
21
+ if (isScannable(path.basename(target)))
22
+ out.push(target);
23
+ return;
24
+ }
25
+ for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
26
+ if (entry.isDirectory() && SKIP_DIRS.has(entry.name))
27
+ continue;
28
+ collect(path.join(target, entry.name), out);
29
+ }
30
+ }
31
+ function resolveProfile(opt, cwd) {
32
+ if (opt !== undefined) {
33
+ if (!DESIGN_PROFILES.includes(opt)) {
34
+ fail(`Unknown profile "${opt}". Use one of: ${DESIGN_PROFILES.join(', ')}`);
35
+ }
36
+ return opt;
37
+ }
38
+ const specs = path.join(cwd, '.ai', 'ui_specs.md');
39
+ const found = fs.existsSync(specs) ? readProfile(fs.readFileSync(specs, 'utf8')) : null;
40
+ if (found)
41
+ return found;
42
+ return fail('No design profile is set. Pass --profile taste-v1|taste-v2, or ask the architect '
43
+ + 'to fill the Design Profile in .ai/ui_specs.md.');
44
+ }
45
+ export function runDesignCheck(paths, options) {
46
+ jsonMode = !!options.json;
47
+ const cwd = process.cwd();
48
+ const profile = resolveProfile(options.profile, cwd);
49
+ const files = [];
50
+ for (const p of paths && paths.length ? paths : ['src']) {
51
+ const abs = path.resolve(cwd, p);
52
+ if (!fs.existsSync(abs))
53
+ fail(`Path not found: ${p}`);
54
+ collect(abs, files);
55
+ }
56
+ const findings = [];
57
+ for (const abs of files) {
58
+ const file = path.relative(cwd, abs).split(path.sep).join('/');
59
+ const text = fs.readFileSync(abs, 'utf8');
60
+ for (const f of checkText(text, path.extname(abs), profile))
61
+ findings.push({ file, ...f });
62
+ }
63
+ const errors = findings.filter((f) => f.severity === 'error').length;
64
+ const warnings = findings.length - errors;
65
+ if (options.json) {
66
+ const out = { profile, filesScanned: files.length, findings, errors, warnings };
67
+ console.log(JSON.stringify(out, null, 2));
68
+ }
69
+ else {
70
+ for (const f of findings) {
71
+ console.log(`${f.file}:${f.line}:${f.column} ${f.rule} ${f.message}`);
72
+ }
73
+ console.log(`${files.length} files scanned (${profile}): ${errors} errors, ${warnings} warnings`);
74
+ }
75
+ process.exit(errors > 0 ? 1 : 0);
76
+ }
@@ -1,3 +1,4 @@
1
1
  export declare function runInit(targetDirArg?: string, options?: {
2
2
  force?: boolean;
3
+ design?: string;
3
4
  }): Promise<void>;
@@ -1,11 +1,13 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+ import { createHash } from 'node:crypto';
4
5
  import pc from 'picocolors';
5
6
  import { detectProject } from '../scanner/detector.js';
6
7
  import { buildContextContent } from '../scanner/context-builder.js';
7
8
  import { runSetup, printSetupResult } from './setup.js';
8
9
  import { stampManaged } from '../core/setup-assets.js';
10
+ import { DESIGN_PROFILES } from '../core/design-rules.js';
9
11
  const __filename = fileURLToPath(import.meta.url);
10
12
  const __dirname = path.dirname(__filename);
11
13
  function getTemplatesDir() {
@@ -16,7 +18,55 @@ function getTemplatesDir() {
16
18
  // Fallback for direct development: src/../templates
17
19
  return path.resolve(__dirname, '../templates');
18
20
  }
21
+ // Sets the Profile line, and the Digest line for taste profiles, in the ui_specs template.
22
+ function applyDesignProfile(spec, profile) {
23
+ let out = spec.replace(/^(- \*\*Profile:\*\*).*$/m, `$1 ${profile}`);
24
+ if (profile.startsWith('taste-')) {
25
+ const digest = `.ai/design/taste/${profile.slice('taste-'.length)}/DIGEST.md`;
26
+ out = out.replace(/^(- \*\*Digest:\*\*).*$/m, `$1 ${digest}`);
27
+ }
28
+ return out;
29
+ }
30
+ function listBundle(dir, base = dir, out = []) {
31
+ if (!fs.existsSync(dir))
32
+ return out;
33
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
34
+ const full = path.join(dir, e.name);
35
+ if (e.isDirectory())
36
+ listBundle(full, base, out);
37
+ else
38
+ out.push(path.relative(base, full));
39
+ }
40
+ return out;
41
+ }
42
+ function installTasteBundle(templatesDir, aiDir, force) {
43
+ const src = path.join(templatesDir, 'design', 'taste');
44
+ const manifestPath = path.join(aiDir, 'design', 'taste', '.manifest.json');
45
+ let manifest = {};
46
+ try {
47
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
48
+ }
49
+ catch { /* none yet */ }
50
+ for (const rel of listBundle(src)) {
51
+ const dest = path.join(aiDir, 'design', 'taste', rel);
52
+ if (fs.existsSync(dest) && !force)
53
+ continue;
54
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
55
+ fs.copyFileSync(path.join(src, rel), dest);
56
+ const text = fs.readFileSync(dest, 'utf8').replace(/\r/g, '');
57
+ manifest[rel.split(path.sep).join('/')] = createHash('sha256').update(text).digest('hex');
58
+ }
59
+ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
60
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
61
+ console.log(pc.green('✔ Installed design bundle .ai/design/taste/'));
62
+ }
19
63
  export async function runInit(targetDirArg, options = {}) {
64
+ if (options.design !== undefined && !DESIGN_PROFILES.includes(options.design)) {
65
+ const list = DESIGN_PROFILES.join(', ');
66
+ console.error(pc.red(`✖ Unknown design profile "${options.design}". Use one of: ${list}`));
67
+ process.exitCode = 1;
68
+ return;
69
+ }
20
70
  const targetDir = path.resolve(targetDirArg || process.cwd());
21
71
  const templatesDir = getTemplatesDir();
22
72
  console.log(pc.bold(pc.cyan('\nNativ: Initializing Multi-Tier Pipeline...')));
@@ -81,9 +131,11 @@ export async function runInit(targetDirArg, options = {}) {
81
131
  const uiTemplate = fs.readFileSync(path.join(templatesDir, 'dot-ai/ui_specs.md'), 'utf8');
82
132
  const uiPath = path.join(aiDir, 'ui_specs.md');
83
133
  if (!fs.existsSync(uiPath) || options.force) {
84
- fs.writeFileSync(uiPath, uiTemplate, 'utf8');
134
+ const uiContent = options.design ? applyDesignProfile(uiTemplate, options.design) : uiTemplate;
135
+ fs.writeFileSync(uiPath, uiContent, 'utf8');
85
136
  console.log(pc.green('✔ Created .ai/ui_specs.md'));
86
137
  }
138
+ installTasteBundle(templatesDir, aiDir, options.force);
87
139
  // 7. Write .ai/master_plan.json
88
140
  const planTemplate = fs.readFileSync(path.join(templatesDir, 'dot-ai/master_plan.json'), 'utf8');
89
141
  const planContent = planTemplate
@@ -96,16 +148,8 @@ export async function runInit(targetDirArg, options = {}) {
96
148
  console.log(pc.green('✔ Created .ai/master_plan.json'));
97
149
  }
98
150
  // 7. Write Sub-agent specifications
99
- const subagentFiles = [
100
- 'backend.md',
101
- 'frontend.md',
102
- 'database.md',
103
- 'qa-tester.md',
104
- 'flutter-developer.md',
105
- 'devops-agent.md',
106
- 'security-auditor.md',
107
- 'db-migration.md',
108
- ];
151
+ const guidesDir = path.join(templatesDir, 'dot-ai/subagents');
152
+ const subagentFiles = fs.existsSync(guidesDir) ? fs.readdirSync(guidesDir).filter((f) => f.endsWith('.md')).sort() : [];
109
153
  for (const file of subagentFiles) {
110
154
  const src = path.join(templatesDir, 'dot-ai/subagents', file);
111
155
  const dest = path.join(subagentsDir, file);
@@ -0,0 +1,2 @@
1
+ /** `nativ notify test`: sends a sample notification so a person can check the webhook or command works. */
2
+ export declare function runNotifyTest(targetDirArg?: string): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import pc from 'picocolors';
2
+ import { resolveProjectRoot } from '../core/root-resolver.js';
3
+ import { loadNotifyConfig, notifyHuman } from '../core/notify.js';
4
+ /** `nativ notify test`: sends a sample notification so a person can check the webhook or command works. */
5
+ export async function runNotifyTest(targetDirArg) {
6
+ const root = resolveProjectRoot(targetDirArg);
7
+ const config = loadNotifyConfig(root);
8
+ if (!config) {
9
+ console.log(pc.yellow('\nNotifications are off. Add to .nativ/config.json:'));
10
+ console.log(pc.white(' { "notify": { "webhook": "https://hooks.slack.com/services/..." } }'));
11
+ console.log(pc.dim(' or "command": "notify-send nativ \\"$(jq -r .text)\\"", or set NATIV_NOTIFY_WEBHOOK / NATIV_NOTIFY_COMMAND.\n'));
12
+ process.exitCode = 1;
13
+ return;
14
+ }
15
+ const result = await notifyHuman(root, { event: config.events[0], taskId: 'task-test', summary: 'This is a test notification from `nativ notify test`', question: 'Did it arrive?', options: ['Yes', 'No'] }, { force: true });
16
+ for (const e of result.errors)
17
+ console.error(pc.red(`✖ ${e}`));
18
+ if (result.sent) {
19
+ const via = [config.webhook && 'webhook', config.command && 'command'].filter(Boolean).join(' and ');
20
+ console.log(pc.green(`\n✔ Test notification sent via ${via}. Events: ${config.events.join(', ')}.\n`));
21
+ }
22
+ else {
23
+ process.exitCode = 1;
24
+ }
25
+ }
@@ -0,0 +1,22 @@
1
+ import { SessionCheckpoint, SessionSummaryEntry } from '../core/session-memory.js';
2
+ export interface SessionSaveOptions {
3
+ summary: string;
4
+ decision?: string[];
5
+ obstacle?: string[];
6
+ next?: string[];
7
+ activeFiles?: string;
8
+ task?: string;
9
+ role?: string;
10
+ sessionId?: string;
11
+ json?: boolean;
12
+ }
13
+ export interface SessionListOptions {
14
+ limit?: number;
15
+ json?: boolean;
16
+ }
17
+ export interface SessionGetOptions {
18
+ json?: boolean;
19
+ }
20
+ export declare function runSessionSave(targetDirArg?: string, options?: SessionSaveOptions): Promise<SessionCheckpoint>;
21
+ export declare function runSessionList(targetDirArg?: string, options?: SessionListOptions): Promise<SessionSummaryEntry[]>;
22
+ export declare function runSessionGet(sessionId?: string, targetDirArg?: string, options?: SessionGetOptions): Promise<SessionCheckpoint | null>;
@@ -0,0 +1,106 @@
1
+ import path from 'node:path';
2
+ import pc from 'picocolors';
3
+ import { saveSessionCheckpoint, getLatestSession, getSession, listSessions, } from '../core/session-memory.js';
4
+ export async function runSessionSave(targetDirArg, options = { summary: '' }) {
5
+ const targetDir = path.resolve(targetDirArg || process.cwd());
6
+ const files = options.activeFiles
7
+ ? options.activeFiles.split(',').map((f) => f.trim()).filter(Boolean)
8
+ : undefined;
9
+ const checkpoint = saveSessionCheckpoint(targetDir, {
10
+ sessionId: options.sessionId,
11
+ summary: options.summary,
12
+ decisions: options.decision,
13
+ obstacles: options.obstacle,
14
+ nextSteps: options.next,
15
+ activeFiles: files,
16
+ taskId: options.task,
17
+ role: options.role,
18
+ });
19
+ if (options.json) {
20
+ console.log(JSON.stringify(checkpoint, null, 2));
21
+ return checkpoint;
22
+ }
23
+ console.log(pc.bold(pc.green(`\n✔ Saved Session Checkpoint: `)) + pc.yellow(`[${checkpoint.sessionId}]`));
24
+ console.log(pc.bold(' Summary: ') + pc.white(checkpoint.summary));
25
+ if (checkpoint.taskId) {
26
+ console.log(pc.bold(' Task: ') + pc.yellow(checkpoint.taskId));
27
+ }
28
+ if (checkpoint.role) {
29
+ console.log(pc.bold(' Role: ') + pc.magenta(checkpoint.role));
30
+ }
31
+ if (checkpoint.decisions && checkpoint.decisions.length > 0) {
32
+ console.log(pc.bold(' Decisions: ') + pc.dim(checkpoint.decisions.join(' | ')));
33
+ }
34
+ if (checkpoint.obstacles && checkpoint.obstacles.length > 0) {
35
+ console.log(pc.bold(' Obstacles: ') + pc.red(checkpoint.obstacles.join(' | ')));
36
+ }
37
+ if (checkpoint.nextSteps && checkpoint.nextSteps.length > 0) {
38
+ console.log(pc.bold(' Next Steps: ') + pc.cyan(checkpoint.nextSteps.join(' | ')));
39
+ }
40
+ if (checkpoint.activeFiles && checkpoint.activeFiles.length > 0) {
41
+ console.log(pc.bold(' Files: ') + pc.dim(checkpoint.activeFiles.join(', ')));
42
+ }
43
+ console.log();
44
+ return checkpoint;
45
+ }
46
+ export async function runSessionList(targetDirArg, options = {}) {
47
+ const targetDir = path.resolve(targetDirArg || process.cwd());
48
+ const sessions = listSessions(targetDir, options.limit || 10);
49
+ if (options.json) {
50
+ console.log(JSON.stringify(sessions, null, 2));
51
+ return sessions;
52
+ }
53
+ console.log(pc.bold(pc.cyan('\nSession Memory Checkpoints (.ai/memory/sessions/)')));
54
+ console.log(pc.dim(`Workspace: ${targetDir}\n`));
55
+ if (sessions.length === 0) {
56
+ console.log(pc.dim(' No saved sessions found.\n'));
57
+ console.log(pc.dim(' Save current progress with: ') + pc.white('nativ session save --summary "..."\n'));
58
+ return sessions;
59
+ }
60
+ for (const s of sessions) {
61
+ const roleTag = s.role ? pc.magenta(`[${s.role}] `) : '';
62
+ const taskTag = s.taskId ? pc.yellow(`[${s.taskId}] `) : '';
63
+ console.log(` ${pc.bold(pc.yellow(s.sessionId))} ${pc.dim(s.timestamp)} ${roleTag}${taskTag}\n ${pc.white(s.summary)}`);
64
+ }
65
+ console.log();
66
+ return sessions;
67
+ }
68
+ export async function runSessionGet(sessionId, targetDirArg, options = {}) {
69
+ const targetDir = path.resolve(targetDirArg || process.cwd());
70
+ const session = sessionId ? getSession(targetDir, sessionId) : getLatestSession(targetDir);
71
+ if (options.json) {
72
+ console.log(JSON.stringify(session, null, 2));
73
+ return session;
74
+ }
75
+ if (!session) {
76
+ console.log(pc.yellow(`\nNo session found${sessionId ? ` matching [${sessionId}]` : ''}.\n`));
77
+ return null;
78
+ }
79
+ console.log(pc.bold(pc.cyan(`\nSession Details: `)) + pc.yellow(`[${session.sessionId}]`));
80
+ console.log(pc.bold(' Timestamp: ') + pc.dim(session.timestamp));
81
+ if (session.taskId)
82
+ console.log(pc.bold(' Task ID: ') + pc.yellow(session.taskId));
83
+ if (session.role)
84
+ console.log(pc.bold(' Role: ') + pc.magenta(session.role));
85
+ console.log(pc.bold(' Summary: ') + pc.white(session.summary));
86
+ if (session.decisions && session.decisions.length > 0) {
87
+ console.log(pc.bold('\n Key Decisions:'));
88
+ for (const d of session.decisions)
89
+ console.log(pc.dim(' • ') + pc.white(d));
90
+ }
91
+ if (session.obstacles && session.obstacles.length > 0) {
92
+ console.log(pc.bold('\n Obstacles / Blockers:'));
93
+ for (const o of session.obstacles)
94
+ console.log(pc.red(' ✖ ') + pc.white(o));
95
+ }
96
+ if (session.nextSteps && session.nextSteps.length > 0) {
97
+ console.log(pc.bold('\n Next Steps:'));
98
+ for (const n of session.nextSteps)
99
+ console.log(pc.cyan(' → ') + pc.white(n));
100
+ }
101
+ if (session.activeFiles && session.activeFiles.length > 0) {
102
+ console.log(pc.bold('\n Active Files: ') + pc.dim(session.activeFiles.join(', ')));
103
+ }
104
+ console.log();
105
+ return session;
106
+ }
@@ -148,7 +148,7 @@ export async function runTaskAdd(title, targetDirArg, options = {}) {
148
148
  plan.overallStatus = 'in_progress';
149
149
  const result = { task, milestoneId: milestone.id, milestoneName: milestone.name, createdMilestone, reopenedMilestone };
150
150
  if (options.json) {
151
- console.log(JSON.stringify(result, null, 2));
151
+ console.log(JSON.stringify(result));
152
152
  return result;
153
153
  }
154
154
  const track = fastPath ? pc.magenta(' [fast path]') : '';