@njeybe/nativ 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +26 -1
  3. package/dist/commands/check.d.ts +3 -0
  4. package/dist/commands/check.js +36 -0
  5. package/dist/commands/doctor.js +7 -1
  6. package/dist/commands/init.d.ts +1 -0
  7. package/dist/commands/init.js +1 -1
  8. package/dist/commands/setup.d.ts +1 -0
  9. package/dist/commands/setup.js +1 -0
  10. package/dist/commands/task/task-add.js +49 -6
  11. package/dist/commands/task/task-common.d.ts +6 -0
  12. package/dist/commands/task/task-lifecycle.d.ts +3 -1
  13. package/dist/commands/task/task-lifecycle.js +40 -2
  14. package/dist/commands/update.d.ts +1 -0
  15. package/dist/commands/update.js +1 -1
  16. package/dist/commands/verify.d.ts +7 -0
  17. package/dist/commands/verify.js +26 -2
  18. package/dist/core/checks/docs-check.d.ts +18 -0
  19. package/dist/core/checks/docs-check.js +186 -0
  20. package/dist/core/code-shape.d.ts +7 -2
  21. package/dist/core/code-shape.js +46 -16
  22. package/dist/core/env-reads.d.ts +15 -0
  23. package/dist/core/env-reads.js +126 -0
  24. package/dist/core/human-gate.js +6 -3
  25. package/dist/core/profiles.d.ts +8 -0
  26. package/dist/core/profiles.js +43 -2
  27. package/dist/core/root-resolver.d.ts +4 -0
  28. package/dist/core/root-resolver.js +32 -0
  29. package/dist/core/setup/gitignore-block.d.ts +17 -0
  30. package/dist/core/setup/gitignore-block.js +182 -0
  31. package/dist/core/setup/pm-playbook.d.ts +1 -1
  32. package/dist/core/setup/pm-playbook.js +1 -1
  33. package/dist/core/setup/setup-planner.js +36 -3
  34. package/dist/core/setup/types.d.ts +6 -0
  35. package/dist/core/verifier/clean-tree.d.ts +14 -0
  36. package/dist/core/verifier/clean-tree.js +151 -0
  37. package/dist/core/verifier/executor.d.ts +3 -1
  38. package/dist/core/verifier/executor.js +54 -31
  39. package/dist/core/verifier/isolation.d.ts +25 -0
  40. package/dist/core/verifier/isolation.js +77 -0
  41. package/dist/core/verifier/portability-lint.d.ts +2 -0
  42. package/dist/core/verifier/portability-lint.js +64 -0
  43. package/dist/core/verifier/skip-policy.d.ts +8 -0
  44. package/dist/core/verifier/skip-policy.js +13 -0
  45. package/dist/core/verifier/task-verifier.d.ts +6 -8
  46. package/dist/core/verifier/task-verifier.js +68 -39
  47. package/dist/core/verifier/types.d.ts +4 -0
  48. package/dist/core/verifier/verify-env.d.ts +2 -0
  49. package/dist/core/verifier/verify-env.js +19 -0
  50. package/dist/index.js +33 -4
  51. package/dist/mcp/server.js +9 -2
  52. package/dist/scanner/types.d.ts +4 -0
  53. package/dist/scanner/types.js +9 -0
  54. package/dist/server/routes/task-routes.d.ts +1 -0
  55. package/dist/server/studio-ui/pages/benchmarks/script.js +9 -8
  56. package/dist/server/studio-ui/pages/benchmarks/styles.js +3 -1
  57. package/dist/server/studio-ui/pages/database/browser.js +1 -1
  58. package/dist/server/studio-ui/pages/database/connection.js +5 -4
  59. package/dist/server/studio-ui/pages/database/data-shell.js +8 -13
  60. package/dist/server/studio-ui/pages/database/drift.js +1 -1
  61. package/dist/server/studio-ui/pages/database/events.js +1 -2
  62. package/dist/server/studio-ui/pages/database/explorer.js +4 -4
  63. package/dist/server/studio-ui/pages/flow/layout.js +1 -1
  64. package/dist/server/studio-ui/pages/flow/styles.js +1 -1
  65. package/dist/server/studio-ui/pages/flow/timeline-view.js +1 -1
  66. package/dist/server/studio-ui/pages/home/script.js +26 -29
  67. package/dist/server/studio-ui/pages/home/styles.js +2 -6
  68. package/dist/server/studio-ui/pages/tasks/script.js +25 -40
  69. package/dist/server/studio-ui/pages/tasks/styles.js +9 -9
  70. package/dist/server/studio-ui/pages/team/script.js +27 -8
  71. package/dist/server/studio-ui/pages/team/styles.js +0 -1
  72. package/dist/server/studio-ui/shell/console.js +13 -7
  73. package/dist/server/studio-ui/shell/detail-view.js +3 -3
  74. package/dist/server/studio-ui/shell/dispatch.js +9 -8
  75. package/dist/server/studio-ui/shell/events-tools.js +11 -0
  76. package/dist/server/studio-ui/shell/format.js +31 -2
  77. package/dist/server/studio-ui/shell/markup-head.js +1 -1
  78. package/dist/server/studio-ui/shell/markup-tools.js +13 -8
  79. package/dist/server/studio-ui/shell/model.js +8 -2
  80. package/dist/server/studio-ui/shell/paint.js +1 -3
  81. package/dist/server/studio-ui/shell/pipeline-state.js +2 -11
  82. package/dist/server/studio-ui/shell/pulse.js +8 -7
  83. package/dist/server/studio-ui/shell/sidebar-view.js +2 -3
  84. package/dist/server/studio-ui/shell/triage.js +1 -1
  85. package/dist/server/studio-ui/styles/console.js +3 -0
  86. package/dist/server/studio-ui/styles/shell.js +4 -6
  87. package/package.json +1 -1
  88. package/plugin/.claude-plugin/plugin.json +1 -1
  89. package/plugin/agents/architect.md +15 -8
  90. package/plugin/agents/verifier.md +16 -11
  91. package/plugin/skills/nativ/SKILL.md +18 -15
  92. package/templates/AGENTS.md +18 -15
  93. package/templates/GEMINI.md +1 -1
  94. package/templates/claude-agents/architect.md +15 -8
  95. package/templates/claude-agents/verifier.md +16 -11
  96. package/templates/dot-ai/subagents/architect.md +2 -2
  97. package/templates/dot-ai/subagents/qa-tester.md +14 -13
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.9.0
4
+
5
+ ### Added
6
+
7
+ - **Clean checks.** A task's check runs in a throwaway copy of the repo with only the files git ships and a minimal environment. Defaults: prototype off, solo on, enterprise on. Use `--isolation none|clean` (or `--clean`) on `nativ verify` and `nativ task complete`. Under enterprise, `--isolation none` on `task complete` needs an interactive terminal. Config keys: `verifyIsolation`, `verifyIsolationFailure`, `verifyEnv`, `verifyLinks`.
8
+ - **Portability warnings.** When a check passes only on your machine, a failure in the clean copy warns "Passes only on this machine". nativ names the test lines that read gitignored files or spawn `nativ`.
9
+ - **Check types.** Tasks take a `verificationKind` (unit, integration, static-config, docs, probe, manual) through `nativ task add --kind`, plus `--notes`. `--kind docs` defaults its check to `nativ check docs`. A manual task needs notes.
10
+ - **Skipped-check policy.** `skippedCheck` allows a skip in prototype, warns in solo and blocks in enterprise. A skip passes quietly only for a manual task with notes. Tasks with no kind only warn.
11
+ - **`nativ check docs <files>`.** Checks backticked file paths, `<PLACEHOLDER>` tokens against the file the paragraph names, setting names used in assignments or as variable references, and nativ subcommands and flags. Supports `--json`. Skip lines with `<!-- nativ-check-ignore -->`, or wrap them in the `-start` and `-end` pair.
12
+ - **Fail-loud config rule.** Code-shape reports "config" findings for reads of settings that silently fall back to empty, such as `?? ''`, `|| ''`, `?? null`, Python `os.getenv('X', '')` and unchecked Go `os.Getenv`. Setting: `codeStyle.configFallback` (off, warn or block; default warn). Opt out per line with `nativ-allow-fallback`.
13
+ - **nativ stays out of git.** `nativ init`, `setup` and `update` add a managed `# >>> nativ (managed) >>>` block to the project's `.gitignore`, listing only files nativ generated. `--git-visibility hidden|shared` sets it (default hidden); shared keeps only secret and machine files ignored. `nativ doctor` warns when the block is missing (`--fix` adds it) and prints the `git rm --cached` command for nativ files already committed. It never runs that command.
14
+ - **`nativ task add --new-milestone <name>`.** Starts the next milestone when the others are done. `--force` works only from the CLI.
15
+
16
+ ### Changed
17
+
18
+ - **Verification environment.** Checks get an allowlisted environment, and a timed-out check stops its whole process tree. `CI` inside a check no longer bypasses human-only steps. A check command may not set `NATIV_CI_OVERRIDE` or `NATIV_HEADLESS_OVERRIDE`; `nativ validate` and `task add` reject it.
19
+ - **`--accept` keeps each value whole.** A `|` no longer splits a criterion.
20
+ - **Guides.** AGENTS.md says required settings fail loudly and tests read only tracked files. The verifier has a "Weak verification" step and a config-fallback checklist item. The architect says what a real check looks like for each kind.
21
+
22
+ <!-- nativ-check-ignore-start -->
23
+ ## 2.8.0
24
+
25
+ ### Changed
26
+
27
+ - **One set of Studio state words.** Every page says Up next, Running, Done or Stuck. "Ready", "Blocked", "Waiting", "Just finished", "Idle" and "completed / started" are gone. A failed run shows as Stuck. A stopped run, or a claim older than 4 hours with no active run, shows as Up next.
28
+ - **Plain agent names.** Home, Tasks and Team show names such as "QA tester" instead of `qa-tester`. Team merges alias rows of the same agent.
29
+ - **Quieter shell.** The pulse strip shows only non-zero counts and hides when everything is zero. The dispatch button hides when nothing is ready instead of showing a grey "Nothing ready". "Abort" is now "Stop". The Benchmarks nav badge is gone. The workspace path no longer breaks mid-word.
30
+ - **Slimmer runner console.** Stop shows only while a run is active. Open diff, Clear and Auto-scroll sit in a More menu. An idle console hides its empty stepper and zero totals.
31
+ - **Tasks page.** A list row opens the task drawer, the same as a Board card. Filters with no tasks are hidden. The Board always shows its four lanes, and an empty lane says "None". The drawer has "Watch live", and "How it is checked" wraps.
32
+ - **Home.** Recent activity shows one row per task with its title and state, for example "Done, took 4m 00s".
33
+ - **Flow.** Plan cards show titles on up to two lines.
34
+ - **Database and Benchmarks.** "Not set up" is neutral, and red means only a configured connection that is offline. The header Connection Settings button is the single way in. Benchmarks lists scenarios that were not run on one line.
35
+ - **Option trade-offs.** The architect, the project manager and the shared directive add one line on when the non-recommended option would be the better choice.
36
+ - **Verifier report.** The verifier reports contract findings and code style findings in separate parts.
37
+
3
38
  ## 2.7.0
4
39
 
5
40
  ### Added
@@ -247,3 +282,4 @@ nativ is now role-based and Claude-first. The roles (architect, project manager,
247
282
  ## 1.0.0
248
283
 
249
284
  Initial release.
285
+ <!-- nativ-check-ignore-end -->
package/README.md CHANGED
@@ -193,6 +193,7 @@ nativ init [targetDir] [-f, --force]
193
193
  | :--- | :--- |
194
194
  | `-f, --force` | Overwrite existing specification and directive files |
195
195
  | `--design <taste-v1|taste-v2>` | Set the design profile in `.ai/ui_specs.md` and install the taste-skill bundle |
196
+ | `--git-visibility <mode>` | `hidden` (default) adds a managed `.gitignore` block listing only nativ's generated files; `shared` ignores only secret and machine files |
196
197
 
197
198
  #### `nativ design check`
198
199
 
@@ -260,6 +261,8 @@ nativ doctor [targetDir] [options]
260
261
  | `--no-deep` | Skip asking Claude Code (`claude mcp list`) whether the server connects |
261
262
  | `--json` | Output the report as JSON |
262
263
 
264
+ It also warns when the managed `.gitignore` block is missing (`--fix` adds it) and prints the `git rm --cached` command for nativ files already committed. It never runs that command.
265
+
263
266
  Run it after updating Claude Code or nativ.
264
267
 
265
268
  #### `nativ update`
@@ -365,8 +368,11 @@ nativ task add <title> [targetDir] [options]
365
368
  | `--deps <ids>` | Comma-separated dependency task IDs |
366
369
  | `--spec-refs <refs>` | Contract anchors, e.g. `ui_specs.md#appointment-list` or `api_contracts.json#GET /appointments` |
367
370
  | `--complexity <level>` | `simple`, `standard` or `complex` (routes the worker's model) |
368
- | `--accept <text>` | "Done when" line; repeatable or `\|`-separated |
371
+ | `--accept <text>` | "Done when" line; repeatable, each value kept whole |
369
372
  | `--fast-path` | Route to the fast-path milestone for quick fixes |
373
+ | `--kind <kind>` | Verification kind: `unit`, `integration`, `static-config`, `docs`, `probe` or `manual`. `docs` defaults the check to `nativ check docs` |
374
+ | `--notes <text>` | Task notes; required with `--kind manual` |
375
+ | `--new-milestone <name>` | Create the next milestone with this name and add the task to it. Refused while another planned milestone is open, unless `--force` |
370
376
  | `--json` | Output the created task as JSON |
371
377
 
372
378
  ```bash
@@ -404,6 +410,8 @@ nativ task complete <taskId> [targetDir] [options]
404
410
  | :--- | :--- |
405
411
  | `-n, --notes <notes>` | Completion notes |
406
412
  | `--timeout <ms>` | Verification timeout (default 120000) |
413
+ | `--isolation <mode>` | Run the check in place (`none`) or in a clean copy of the repo (`clean`). Accepts `none` or `clean`. See [Verification](#verification) |
414
+ | `--clean` | Short for `--isolation clean` |
407
415
  | `--no-verify` | Skip verification. *You only*: needs an interactive terminal, is denied to agents, and the MCP tool refuses it |
408
416
 
409
417
  #### `nativ task block`
@@ -475,6 +483,8 @@ nativ verify --all --json
475
483
  | `-a, --all` | Every completed task |
476
484
  | `-m, --milestone <id>` | Every task in a milestone |
477
485
  | `--timeout <ms>` | Per command (default 120000) |
486
+ | `--isolation <mode>` | In place, or in a clean copy with only the files git ships and a minimal environment |
487
+ | `--clean` | Short for `--isolation clean` |
478
488
  | `--json` | Output as JSON |
479
489
 
480
490
  **What runs, in order** (the same for `verify`, the `task complete` gatekeeper and Studio dispatch):
@@ -492,6 +502,16 @@ nativ verify --all --json
492
502
 
493
503
  With no phases configured, verification is just the task's command and the style check. The settings always come from the main checkout, never a task worktree's copy. If `.nativ/config.json` cannot be read, or a phase has no `name` or `run`, verification fails with a clear message instead of skipping the checks.
494
504
 
505
+ #### `nativ check docs`
506
+
507
+ Checks markdown files for drift: backticked file paths, `<PLACEHOLDER>` tokens against the file the paragraph names, setting names used in assignments or as variable references, and nativ subcommands and flags.
508
+
509
+ ```bash
510
+ nativ check docs README.md CHANGELOG.md [--json]
511
+ ```
512
+
513
+ Skip a line with `<!-- nativ-check-ignore -->`, or wrap a block in `<!-- nativ-check-ignore-start -->` and `<!-- nativ-check-ignore-end -->`.
514
+
495
515
  [Back to contents](#contents)
496
516
 
497
517
  ---
@@ -722,6 +742,11 @@ Written by `setup`; every key is optional.
722
742
  | `codeStyle.mode` | `warn` | `warn`, `block` or `off` for the line-length and comment check |
723
743
  | `codeStyle.maxLineLength` | `120` | Longest allowed line |
724
744
  | `codeStyle.maxCommentLines` | `2` | Longest allowed comment block |
745
+ | `codeStyle.configFallback` | `warn` | `off`, `warn` or `block` for reads of settings that silently fall back to empty. Opt out per line with `nativ-allow-fallback` |
746
+ | `verifyIsolation` | `none` (`clean` in solo and enterprise) | Run checks in a clean copy of the repo. See [`nativ verify`](#verification) |
747
+ | `verifyIsolationFailure` | `warn` | `warn` or `block` when a check fails in the clean copy but passes in place |
748
+ | `verifyEnv` | none | Extra environment variable names a check may read |
749
+ | `verifyLinks` | `node_modules`, `.venv`, `venv` | Files linked into the clean copy, for example dependency folders |
725
750
  | `providers` | see [Providers](#providers) | Provider order per role: `architect`, `pm`, `worker`, `verifier`, `triage` |
726
751
  | `models` | provider default | Model per provider |
727
752
  | `notify` | off | `webhook`, `command` and `events` for human notifications. See [`nativ notify test`](#nativ-notify-test) |
@@ -0,0 +1,3 @@
1
+ export declare function runCheckDocs(files: string[], options: {
2
+ json?: boolean;
3
+ }): Promise<void>;
@@ -0,0 +1,36 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { resolveProjectRoot } from '../core/root-resolver.js';
4
+ import { listProjectEnvNames } from '../core/env-reads.js';
5
+ import { checkDocText } from '../core/checks/docs-check.js';
6
+ function specOf(cmd) {
7
+ const flags = cmd.options.flatMap((o) => [o.long, o.short].filter((f) => !!f));
8
+ const subs = {};
9
+ for (const sub of cmd.commands)
10
+ subs[sub.name()] = specOf(sub);
11
+ return { flags, subs };
12
+ }
13
+ export async function runCheckDocs(files, options) {
14
+ const root = resolveProjectRoot(process.cwd());
15
+ const { createProgram } = await import('../index.js');
16
+ const ctx = { root, envNames: new Set(listProjectEnvNames(root)), program: specOf(createProgram()) };
17
+ const findings = [];
18
+ for (const f of files) {
19
+ const full = path.resolve(process.cwd(), f);
20
+ if (!fs.existsSync(full)) {
21
+ console.error(`Cannot read ${f}`);
22
+ process.exitCode = 1;
23
+ return;
24
+ }
25
+ findings.push(...checkDocText(fs.readFileSync(full, 'utf8'), path.relative(root, full), ctx));
26
+ }
27
+ if (options.json)
28
+ console.log(JSON.stringify({ ok: findings.length === 0, findings }, null, 2));
29
+ else if (findings.length === 0)
30
+ console.log('Docs check: no problems found.');
31
+ else
32
+ for (const x of findings)
33
+ console.log(`${x.file}:${x.line} ${x.kind} ${x.message}`);
34
+ if (findings.length > 0)
35
+ process.exitCode = 1;
36
+ }
@@ -100,7 +100,11 @@ export function collectChecks(root, options = {}) {
100
100
  add('cli-runs', version.ok ? 'ok' : 'fail', version.ok ? `\`${cli.join(' ')}\` runs (${version.output})` : `\`${cli.join(' ')}\` does not run: ${version.output || 'no output'}. The hooks and MCP server would fail.`);
101
101
  // Generated assets: whatever setup would still change is drift
102
102
  for (const change of plan.changes) {
103
- if (change.action === 'created')
103
+ if (change.path === '.gitignore' && change.action !== 'unchanged') {
104
+ const what = change.action === 'created' ? 'has no' : 'has an outdated';
105
+ add('gitignore', 'warn', `.gitignore ${what} nativ block, so generated files may be committed by accident`, true);
106
+ }
107
+ else if (change.action === 'created')
104
108
  add(`asset:${change.path}`, 'fail', `${change.path} is missing`, true);
105
109
  else if (change.action === 'updated')
106
110
  add(`asset:${change.path}`, 'warn', `${change.path} is out of date${change.detail ? ` (${change.detail})` : ''}`, true);
@@ -109,6 +113,8 @@ export function collectChecks(root, options = {}) {
109
113
  else if (change.detail)
110
114
  add(`asset:${change.path}`, 'info', `${change.path}: ${change.detail}`);
111
115
  }
116
+ if (plan.gitRmCommand)
117
+ add('gitignore-tracked', 'info', `nativ files are already tracked by git. To stop tracking them, run: ${plan.gitRmCommand}`);
112
118
  if (!plan.changes.some((c) => c.action === 'created' || c.action === 'updated'))
113
119
  add('assets', 'ok', 'MCP server, hooks, permissions, agents and directives are in place');
114
120
  // Claude Code's own view (opt-in: it connects to every configured MCP server)
@@ -1,4 +1,5 @@
1
1
  export declare function runInit(targetDirArg?: string, options?: {
2
2
  force?: boolean;
3
3
  design?: string;
4
+ gitVisibility?: 'hidden' | 'shared';
4
5
  }): Promise<void>;
@@ -175,7 +175,7 @@ export async function runInit(targetDirArg, options = {}) {
175
175
  }
176
176
  // 10. Claude Code configuration: MCP server, hooks, permissions, agents, AGENTS.md. Merges; never overwrites.
177
177
  console.log(pc.yellow('Configuring Claude Code (MCP server, hooks, agents)...'));
178
- const setup = await runSetup(targetDir, { quiet: true });
178
+ const setup = await runSetup(targetDir, { quiet: true, gitVisibility: options.gitVisibility });
179
179
  if (setup) {
180
180
  printSetupResult(setup);
181
181
  }
@@ -4,6 +4,7 @@ export interface SetupCommandOptions {
4
4
  enforcement?: string;
5
5
  dryRun?: boolean;
6
6
  force?: boolean;
7
+ gitVisibility?: 'hidden' | 'shared';
7
8
  json?: boolean;
8
9
  /** Suppresses the report; `nativ init` prints its own. */
9
10
  quiet?: boolean;
@@ -32,6 +32,7 @@ export async function runSetup(targetDirArg, options = {}) {
32
32
  command: options.command,
33
33
  enforcement: options.enforcement,
34
34
  force: options.force,
35
+ gitVisibility: options.gitVisibility,
35
36
  dryRun: options.dryRun,
36
37
  };
37
38
  const result = applySetup(targetDir, setupOptions);
@@ -1,5 +1,7 @@
1
1
  import pc from 'picocolors';
2
2
  import { isSubagentType, SUBAGENT_TYPES, validateMasterPlanTask, } from '../../scanner/types.js';
3
+ import { configuredInvocation } from '../../core/setup/setup-planner.js';
4
+ import { cliString, resolveCliInvocation } from '../../core/setup/types.js';
3
5
  import { withPlanLock } from '../../core/lock-manager.js';
4
6
  import { getPlanPath, getRoleGuide, } from './task-common.js';
5
7
  export const FAST_PATH_MILESTONE_NAME = 'Fast-Path Tasks';
@@ -43,6 +45,15 @@ function failTaskAdd(code, message, json) {
43
45
  process.exitCode = 1;
44
46
  return null;
45
47
  }
48
+ /** Default check for a docs task: `nativ check docs` on its markdown files, or '' without any. */
49
+ function defaultDocsCheck(targetFiles) {
50
+ const docs = targetFiles.filter((f) => /\.md$/i.test(f));
51
+ if (!docs.length)
52
+ return '';
53
+ const root = process.cwd();
54
+ const cli = cliString(configuredInvocation(root) ?? resolveCliInvocation({}));
55
+ return `${cli} check docs ${docs.join(' ')}`;
56
+ }
46
57
  /** Resolves a milestone by exact ID, exact name, or a unique partial name (all case-insensitive). */
47
58
  function findMilestone(plan, query) {
48
59
  const q = query.trim().toLowerCase();
@@ -86,7 +97,29 @@ export async function runTaskAdd(title, targetDirArg, options = {}) {
86
97
  const fastPath = options.fastPath === true;
87
98
  let milestone;
88
99
  let createdMilestone = false;
89
- if (options.milestone) {
100
+ const newName = options.newMilestone?.trim();
101
+ if (options.newMilestone !== undefined && !newName) {
102
+ ctx.abort();
103
+ return failTaskAdd('INVALID_MILESTONE_NAME', 'A new milestone needs a name', options.json);
104
+ }
105
+ if (newName && (options.milestone || fastPath)) {
106
+ ctx.abort();
107
+ return failTaskAdd('CONFLICTING_OPTIONS', '--new-milestone cannot be used with --milestone or --fast-path', options.json);
108
+ }
109
+ const stillOpen = plan.milestones.filter((m) => !m.fastPath && m.status !== 'completed');
110
+ if (newName && stillOpen.length && !options.force) {
111
+ ctx.abort();
112
+ const ids = stillOpen.map((m) => m.id).join(', ');
113
+ return failTaskAdd('MILESTONE_STILL_OPEN', `Milestone ${ids} is not completed yet. Finish it first, or pass --force`, options.json);
114
+ }
115
+ if (newName) {
116
+ milestone = { id: generateNextMilestoneId(plan), name: newName, status: 'in_progress', tasks: [] };
117
+ plan.milestones.push(milestone);
118
+ plan.activeMilestoneId = milestone.id;
119
+ plan.overallStatus = 'in_progress';
120
+ createdMilestone = true;
121
+ }
122
+ else if (options.milestone) {
90
123
  const found = findMilestone(plan, options.milestone);
91
124
  if (Array.isArray(found)) {
92
125
  ctx.abort();
@@ -116,23 +149,33 @@ export async function runTaskAdd(title, targetDirArg, options = {}) {
116
149
  }
117
150
  const specRefs = [...new Set(splitList(options.specRefs))];
118
151
  const acceptanceCriteria = (Array.isArray(options.accept) ? options.accept : options.accept ? [options.accept] : [])
119
- .flatMap((item) => item.split('|'))
120
152
  .map((item) => item.trim())
121
153
  .filter(Boolean);
122
154
  const complexity = options.complexity?.trim().toLowerCase();
155
+ const kind = options.kind?.trim().toLowerCase();
156
+ const notes = (options.notes ?? '').trim();
157
+ if (kind === 'manual' && !notes) {
158
+ ctx.abort();
159
+ return failTaskAdd('MANUAL_NEEDS_NOTES', 'A manual check needs --notes saying what the person must confirm', options.json);
160
+ }
161
+ const files = splitList(options.files);
162
+ let verify = (options.verify ?? '').trim();
163
+ if (kind === 'docs' && !verify)
164
+ verify = defaultDocsCheck(files);
123
165
  const task = {
124
166
  id: generateNextTaskId(plan),
125
167
  title: cleanTitle,
126
168
  description: (options.description ?? '').trim(),
127
169
  assignedSubagent: agent,
128
170
  dependencies: deps,
129
- targetFiles: splitList(options.files),
171
+ targetFiles: files,
130
172
  status: 'pending',
131
- verificationCommand: (options.verify ?? '').trim(),
132
- notes: (options.notes ?? '').trim(),
173
+ verificationCommand: verify,
174
+ notes,
133
175
  ...(fastPath ? { fastPath: true } : {}),
134
176
  ...(specRefs.length ? { specRefs } : {}),
135
177
  ...(complexity ? { complexity: complexity } : {}),
178
+ ...(kind ? { verificationKind: kind } : {}),
136
179
  ...(acceptanceCriteria.length ? { acceptanceCriteria } : {}),
137
180
  };
138
181
  const problems = validateMasterPlanTask(task);
@@ -154,7 +197,7 @@ export async function runTaskAdd(title, targetDirArg, options = {}) {
154
197
  const track = fastPath ? pc.magenta(' [fast path]') : '';
155
198
  console.log(pc.green(`\n✔ Task [${pc.bold(task.id)}] added to [${milestone.id}] ${milestone.name}`) + track);
156
199
  if (createdMilestone)
157
- console.log(pc.cyan(` Created fast-path milestone [${milestone.id}]`));
200
+ console.log(pc.cyan(` Created ${fastPath ? 'fast-path ' : ''}milestone [${milestone.id}]`));
158
201
  if (reopenedMilestone)
159
202
  console.log(pc.yellow(` Reopened completed milestone [${milestone.id}]`));
160
203
  console.log(pc.dim(' Title: ') + task.title);
@@ -34,8 +34,14 @@ export interface TaskAddOptions {
34
34
  specRefs?: string | string[];
35
35
  /** simple | standard | complex. */
36
36
  complexity?: string;
37
+ /** unit | integration | static-config | docs | probe | manual. */
38
+ kind?: string;
37
39
  /** "Done when" lines: an array, or one string with "|" separators. */
38
40
  accept?: string | string[];
41
+ /** Create the next milestone with this name and add the task to it. */
42
+ newMilestone?: string;
43
+ /** With newMilestone, allow it while a planned milestone is still open (CLI only). */
44
+ force?: boolean;
39
45
  json?: boolean;
40
46
  }
41
47
  export interface TaskAddResult {
@@ -7,5 +7,7 @@ export interface TaskStartOptions {
7
7
  }
8
8
  export declare function runTaskStart(taskId: string, targetDirArg?: string, options?: TaskStartOptions): Promise<void | null>;
9
9
  export declare function runTaskUnlock(taskId: string, targetDirArg?: string, options?: TaskUnlockOptions): Promise<void>;
10
- export declare function runTaskComplete(taskId: string, targetDirArg?: string, options?: TaskCompleteOptions): Promise<void>;
10
+ export declare function runTaskComplete(taskId: string, targetDirArg?: string, options?: TaskCompleteOptions & {
11
+ isolation?: string;
12
+ }): Promise<void>;
11
13
  export declare function runTaskBlock(taskId: string, reason: string, targetDirArg?: string): Promise<void | null>;
@@ -1,5 +1,10 @@
1
+ import path from 'node:path';
1
2
  import pc from 'picocolors';
3
+ import { parseIsolation } from '../verify.js';
4
+ import { resolveActiveProfileSettings } from '../../core/profiles.js';
5
+ import { resolveMainRoot } from '../../core/root-resolver.js';
2
6
  import { runTaskVerification } from '../../core/verifier.js';
7
+ import { evaluateSkip } from '../../core/verifier/skip-policy.js';
3
8
  import { digestFailure } from '../../core/verifier/output-digest.js';
4
9
  import { CircuitBreaker, TestIntegrityGuard, resolveGitHead, closeEscalationsForTask, } from '../../governor/index.js';
5
10
  import { loadPlan, withPlanLock } from '../../core/lock-manager.js';
@@ -91,6 +96,23 @@ export async function runTaskUnlock(taskId, targetDirArg, options = {}) {
91
96
  export async function runTaskComplete(taskId, targetDirArg, options = {}) {
92
97
  if (options.skipVerify && refuseHeadless('nativ task complete --no-verify'))
93
98
  return;
99
+ let isolation;
100
+ try {
101
+ isolation = parseIsolation(options);
102
+ }
103
+ catch (e) {
104
+ console.error(pc.red(`
105
+ ✖ ${e.message}
106
+ `));
107
+ process.exitCode = 1;
108
+ return;
109
+ }
110
+ const root = resolveMainRoot(path.resolve(targetDirArg || process.cwd()));
111
+ const profile = resolveActiveProfileSettings(root);
112
+ if (isolation === 'none' &&
113
+ profile.verifyIsolationFailure === 'block' &&
114
+ refuseHeadless('nativ task complete --isolation none'))
115
+ return;
94
116
  const { targetDir, planPath } = getPlanPath(targetDirArg);
95
117
  const plan = loadPlan(planPath);
96
118
  if (!plan) {
@@ -145,7 +167,7 @@ export async function runTaskComplete(taskId, targetDirArg, options = {}) {
145
167
  // ── Verification Gatekeeper ────────────────────────────────────────────────
146
168
  let vResult = null;
147
169
  if (!options.skipVerify) {
148
- vResult = await runTaskVerification(foundTask, { cwd: targetDir, timeout: options.timeout });
170
+ vResult = await runTaskVerification(foundTask, { cwd: targetDir, timeout: options.timeout, isolation });
149
171
  if (!vResult.success) {
150
172
  console.error(pc.red(`\n✖ Task [${pc.bold(taskId)}] verification FAILED with exit code ${vResult.exitCode}:`));
151
173
  const failedPhase = vResult.phases?.find((p) => !p.success);
@@ -173,11 +195,27 @@ export async function runTaskComplete(taskId, targetDirArg, options = {}) {
173
195
  return;
174
196
  }
175
197
  if (vResult.skipped) {
198
+ const skip = evaluateSkip(foundTask, profile.skippedCheck);
199
+ if (skip.action === 'block') {
200
+ console.error(pc.red(`
201
+ ✖ Task [${pc.bold(taskId)}] cannot complete: ${skip.message}`));
202
+ console.error(pc.yellow(` Task remains '${foundTask.status}'.
203
+ `));
204
+ process.exitCode = 1;
205
+ return;
206
+ }
207
+ if (skip.action === 'warn')
208
+ console.log(pc.yellow(`
209
+ ⚠ ${skip.message}`));
176
210
  console.log(pc.dim(`\n○ Verification skipped: no verification command specified for [${taskId}]`));
177
211
  }
178
212
  else {
179
213
  const elapsed = (vResult.durationMs / 1000).toFixed(2);
180
- console.log(pc.green(`\n✔ Verification passed (${elapsed}s): `) + pc.yellow(`\`${foundTask.verificationCommand}\``));
214
+ const used = vResult.isolation ?? 'none';
215
+ console.log(pc.green(`\n✔ Verification passed (${elapsed}s, isolation: ${used}): `) +
216
+ pc.yellow(`\`${foundTask.verificationCommand}\``));
217
+ for (const w of vResult.warnings ?? [])
218
+ console.log(pc.yellow(` ⚠ ${w}`));
181
219
  }
182
220
  }
183
221
  else {
@@ -1,6 +1,7 @@
1
1
  export interface UpdateOptions {
2
2
  /** Also replace directive and role-guide files that were edited by hand. */
3
3
  force?: boolean;
4
+ gitVisibility?: 'hidden' | 'shared';
4
5
  }
5
6
  /**
6
7
  * `nativ update`: brings a project up to the current templates without destroying its own work.
@@ -110,7 +110,7 @@ export async function runUpdate(targetDirArg, options = {}) {
110
110
  kept += syncTasteBundle(templatesDir, aiDir, options.force);
111
111
  // 2. Everything `nativ setup` manages: AGENTS.md, .claude/agents/, settings, MCP server
112
112
  console.log(pc.bold('\nClaude Code configuration'));
113
- const setup = applySetup(targetDir, { force: options.force });
113
+ const setup = applySetup(targetDir, { force: options.force, gitVisibility: options.gitVisibility });
114
114
  printSetupResult(setup);
115
115
  // 3. Backfill missing core contracts safely (NEVER overwrite existing project state)
116
116
  console.log(pc.bold('\nContracts'));
@@ -4,5 +4,12 @@ export interface VerifyOptions {
4
4
  milestone?: string;
5
5
  json?: boolean;
6
6
  timeout?: number;
7
+ isolation?: string;
8
+ clean?: boolean;
7
9
  }
10
+ /** Reads --isolation / --clean; undefined means use the profile. */
11
+ export declare function parseIsolation(o: {
12
+ isolation?: string;
13
+ clean?: boolean;
14
+ }): 'none' | 'clean' | undefined;
8
15
  export declare function runVerify(taskId?: string, targetDirArg?: string, options?: VerifyOptions): Promise<BatchVerificationResult>;
@@ -1,6 +1,9 @@
1
1
  import path from 'node:path';
2
2
  import pc from 'picocolors';
3
3
  import { verifyBatch } from '../core/verifier.js';
4
+ import { loadPlan } from '../core/lock-manager.js';
5
+ import { evaluateSkip } from '../core/verifier/skip-policy.js';
6
+ import { resolveActiveProfileSettings } from '../core/profiles.js';
4
7
  import { digestFailure } from '../core/verifier/output-digest.js';
5
8
  /** Passing runs need no output; failing ones get a digest and the path to the full log. */
6
9
  function compactBatch(batch, root) {
@@ -14,6 +17,15 @@ function compactBatch(batch, root) {
14
17
  }),
15
18
  };
16
19
  }
20
+ /** Reads --isolation / --clean; undefined means use the profile. */
21
+ export function parseIsolation(o) {
22
+ const v = o.clean ? 'clean' : o.isolation;
23
+ if (v === undefined)
24
+ return undefined;
25
+ if (v !== 'none' && v !== 'clean')
26
+ throw new Error(`--isolation must be none or clean, got "${v}"`);
27
+ return v;
28
+ }
17
29
  export async function runVerify(taskId, targetDirArg, options = {}) {
18
30
  const targetDir = path.resolve(targetDirArg || process.cwd());
19
31
  const batch = await verifyBatch(targetDir, {
@@ -21,6 +33,7 @@ export async function runVerify(taskId, targetDirArg, options = {}) {
21
33
  milestoneId: options.milestone,
22
34
  all: options.all,
23
35
  timeout: options.timeout,
36
+ isolation: parseIsolation(options),
24
37
  });
25
38
  if (options.json) {
26
39
  console.log(JSON.stringify(compactBatch(batch, targetDir)));
@@ -35,24 +48,35 @@ export async function runVerify(taskId, targetDirArg, options = {}) {
35
48
  console.log(pc.yellow('No candidate tasks found to verify. Specify a taskId, --milestone, or --all.\n'));
36
49
  return batch;
37
50
  }
51
+ const skipMode = resolveActiveProfileSettings(targetDir).skippedCheck;
52
+ const plan = loadPlan(path.join(targetDir, '.ai', 'master_plan.json'), { silent: true });
53
+ const planTasks = plan?.milestones.flatMap((m) => m.tasks) ?? [];
38
54
  for (const t of batch.tasks) {
39
55
  const elapsed = (t.result.durationMs / 1000).toFixed(2);
40
56
  if (t.result.skipped) {
41
57
  console.log(pc.dim(` ○ [${t.taskId}] ${t.title} — no verification command (skipped)`));
58
+ const pt = planTasks.find((x) => x.id === t.taskId);
59
+ const skip = pt && evaluateSkip(pt, skipMode);
60
+ if (skip?.message)
61
+ console.log(pc.yellow(` ⚠ ${skip.message}`));
42
62
  continue;
43
63
  }
44
64
  if (t.result.success) {
45
65
  console.log(pc.green(` ✔ [${pc.bold(t.taskId)}] ${t.title}`) +
46
- pc.dim(` (${elapsed}s)`) +
66
+ pc.dim(` (${elapsed}s, isolation: ${t.result.isolation ?? 'none'})`) +
47
67
  pc.yellow(` \`${t.result.command}\``));
68
+ for (const w of t.result.warnings ?? [])
69
+ console.log(pc.yellow(` ⚠ ${w}`));
48
70
  if (t.result.phases?.length) {
49
71
  console.log(pc.dim(` Phases passed: ${t.result.phases.map((p) => p.name).join(', ')}`));
50
72
  }
51
73
  }
52
74
  else {
53
75
  console.log(pc.red(` ✖ [${pc.bold(t.taskId)}] ${t.title}`) +
54
- pc.dim(` (${elapsed}s, exit code: ${t.result.exitCode})`) +
76
+ pc.dim(` (${elapsed}s, exit ${t.result.exitCode}, isolation: ${t.result.isolation ?? 'none'})`) +
55
77
  pc.yellow(` \`${t.result.command}\``));
78
+ for (const w of t.result.warnings ?? [])
79
+ console.log(pc.yellow(` ⚠ ${w}`));
56
80
  const failedPhase = t.result.phases?.find((p) => !p.success);
57
81
  if (failedPhase)
58
82
  console.log(pc.red(` Failed in phase "${failedPhase.name}"`));
@@ -0,0 +1,18 @@
1
+ /** One nativ command: its own flags and its subcommands. */
2
+ export interface CommandSpec {
3
+ flags: string[];
4
+ subs: Record<string, CommandSpec>;
5
+ }
6
+ export interface DocFinding {
7
+ file: string;
8
+ line: number;
9
+ kind: 'path' | 'placeholder' | 'setting' | 'flag';
10
+ token: string;
11
+ message: string;
12
+ }
13
+ export interface DocsContext {
14
+ root: string;
15
+ envNames: Set<string>;
16
+ program: CommandSpec;
17
+ }
18
+ export declare function checkDocText(text: string, docFile: string, ctx: DocsContext): DocFinding[];