@njeybe/nativ 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +71 -1
- package/dist/commands/design-check.d.ts +4 -0
- package/dist/commands/design-check.js +76 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +53 -1
- package/dist/commands/update.js +46 -0
- package/dist/core/design-rules.d.ts +23 -0
- package/dist/core/design-rules.js +115 -0
- package/dist/core/harness-integrity.js +26 -7
- package/dist/core/setup/pm-playbook.d.ts +1 -1
- package/dist/core/setup/pm-playbook.js +1 -1
- package/dist/index.js +11 -0
- package/dist/server/studio-ui/pages/benchmarks/script.js +9 -8
- package/dist/server/studio-ui/pages/benchmarks/styles.js +11 -6
- package/dist/server/studio-ui/pages/database/browser.js +2 -2
- package/dist/server/studio-ui/pages/database/connection.js +6 -5
- package/dist/server/studio-ui/pages/database/data-shell.js +12 -17
- package/dist/server/studio-ui/pages/database/drift.js +2 -2
- package/dist/server/studio-ui/pages/database/events.js +1 -2
- package/dist/server/studio-ui/pages/database/explorer.js +6 -6
- package/dist/server/studio-ui/pages/database/styles.js +5 -5
- package/dist/server/studio-ui/pages/flow/layout.js +1 -1
- package/dist/server/studio-ui/pages/flow/styles.js +4 -4
- package/dist/server/studio-ui/pages/flow/timeline-view.js +1 -1
- package/dist/server/studio-ui/pages/home/script.js +108 -92
- package/dist/server/studio-ui/pages/home/styles.js +29 -45
- package/dist/server/studio-ui/pages/tasks/script.js +146 -14
- package/dist/server/studio-ui/pages/tasks/styles.js +63 -6
- package/dist/server/studio-ui/pages/team/script.js +69 -53
- package/dist/server/studio-ui/pages/team/styles.js +20 -20
- package/dist/server/studio-ui/pages/worktrees/script.js +47 -35
- package/dist/server/studio-ui/pages/worktrees/styles.js +12 -10
- package/dist/server/studio-ui/shell/brand.d.ts +4 -0
- package/dist/server/studio-ui/shell/brand.js +16 -0
- package/dist/server/studio-ui/shell/console.js +71 -6
- package/dist/server/studio-ui/shell/core.js +8 -8
- package/dist/server/studio-ui/shell/detail-view.js +4 -4
- package/dist/server/studio-ui/shell/dispatch.js +23 -4
- package/dist/server/studio-ui/shell/events-tools.js +16 -1
- package/dist/server/studio-ui/shell/format.js +36 -8
- package/dist/server/studio-ui/shell/icons.js +23 -15
- package/dist/server/studio-ui/shell/markup-head.js +11 -6
- package/dist/server/studio-ui/shell/markup-tools.js +29 -15
- package/dist/server/studio-ui/shell/model.js +8 -2
- package/dist/server/studio-ui/shell/paint.js +4 -3
- package/dist/server/studio-ui/shell/pipeline-state.js +2 -11
- package/dist/server/studio-ui/shell/proposals.js +6 -2
- package/dist/server/studio-ui/shell/pulse.d.ts +1 -0
- package/dist/server/studio-ui/shell/pulse.js +68 -0
- package/dist/server/studio-ui/shell/router.js +25 -0
- package/dist/server/studio-ui/shell/sidebar-view.js +7 -5
- package/dist/server/studio-ui/shell/toasts-format.js +4 -4
- package/dist/server/studio-ui/shell/triage.js +1 -1
- package/dist/server/studio-ui/styles/console.js +36 -13
- package/dist/server/studio-ui/styles/dialogs.js +4 -4
- package/dist/server/studio-ui/styles/responsive.js +8 -0
- package/dist/server/studio-ui/styles/shell.js +23 -6
- package/dist/server/studio-ui/styles/strategist.js +10 -10
- package/dist/server/studio-ui/styles/tokens-base.js +23 -23
- package/dist/server/studio-ui.js +2 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/agents/architect.md +11 -12
- package/plugin/agents/verifier.md +4 -1
- package/plugin/skills/nativ/SKILL.md +7 -7
- package/templates/AGENTS.md +7 -7
- package/templates/GEMINI.md +4 -3
- package/templates/claude-agents/architect.md +11 -12
- package/templates/claude-agents/verifier.md +4 -1
- package/templates/design/taste/LICENSE +21 -0
- package/templates/design/taste/SOURCE.md +28 -0
- package/templates/design/taste/v1/DIGEST.md +57 -0
- package/templates/design/taste/v1/SKILL.md +226 -0
- package/templates/design/taste/v2/DIGEST.md +62 -0
- package/templates/design/taste/v2/SKILL.md +1206 -0
- package/templates/dot-ai/subagents/architect.md +8 -2
- package/templates/dot-ai/subagents/flutter-developer.md +2 -11
- package/templates/dot-ai/subagents/frontend.md +47 -44
- package/templates/dot-ai/ui_specs.md +15 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.8.0
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **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.
|
|
8
|
+
- **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.
|
|
9
|
+
- **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.
|
|
10
|
+
- **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.
|
|
11
|
+
- **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.
|
|
12
|
+
- **Home.** Recent activity shows one row per task with its title and state, for example "Done, took 4m 00s".
|
|
13
|
+
- **Flow.** Plan cards show titles on up to two lines.
|
|
14
|
+
- **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.
|
|
15
|
+
- **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.
|
|
16
|
+
- **Verifier report.** The verifier reports contract findings and code style findings in separate parts.
|
|
17
|
+
|
|
18
|
+
## 2.7.0
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **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.
|
|
23
|
+
- **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`.
|
|
24
|
+
- **`nativ init --design`.** Choose the design profile when scaffolding a project.
|
|
25
|
+
- **`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`.
|
|
26
|
+
- **Frontend pre-flight.** The frontend guide runs `nativ design check` on the task's UI files before a worker finishes.
|
|
27
|
+
- **Doctor findings.** `nativ doctor` reports the design profile (`specs:design-profile`) and the bundle (`design:taste-bundle`).
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **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.
|
|
32
|
+
- **New wordmark.** The nativ wordmark replaces the old letter logo and follows the theme.
|
|
33
|
+
- **Phosphor icons.** Studio icons are now Phosphor (MIT). See Third-party notices in the README.
|
|
34
|
+
- **Pulse strip.** A strip under the top bar shows milestone progress and running, blocked and decision counts on every page.
|
|
35
|
+
- **Dispatch button.** The top bar has a "Dispatch `<next task>`" button.
|
|
36
|
+
- **Inspector.** The console drawer is now a right-side inspector. On narrow screens it is a sheet.
|
|
37
|
+
- **Home is a "now" page.** It shows decisions, running tasks, up next, milestone progress and recent activity.
|
|
38
|
+
- **Tasks list.** Tasks defaults to a list grouped by milestone, with filters and a Board toggle.
|
|
39
|
+
- **Team roster.** Team is a roster table.
|
|
40
|
+
- **Worktrees.** Worktree changes open in the inspector.
|
|
41
|
+
- **Restyled pages.** Flow, Benchmarks, Database and dialogs are restyled.
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
|
|
45
|
+
- 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.
|
|
46
|
+
- The Anti-Generic Checklist (Web, Marketing pages, Mobile) from `ui_specs.md`, and the Transitions and Active state lines of its Accessibility section.
|
|
47
|
+
- 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.
|
|
48
|
+
- Flutter guide: Anti-generic rules.
|
|
49
|
+
- Architect guides: the `nativ` profile choice, the Inter typeface rule and the Anti-Generic and Marketing pages checks.
|
|
50
|
+
- Doctor finding `specs:marketing`.
|
|
51
|
+
- `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.
|
|
52
|
+
|
|
3
53
|
## 2.6.0
|
|
4
54
|
|
|
5
55
|
### Improved: token usage and autonomy
|
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) |
|
|
@@ -190,6 +192,20 @@ nativ init [targetDir] [-f, --force]
|
|
|
190
192
|
| Option | Description |
|
|
191
193
|
| :--- | :--- |
|
|
192
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 |
|
|
193
209
|
|
|
194
210
|
#### `nativ setup`
|
|
195
211
|
|
|
@@ -656,6 +672,29 @@ Run the MCP server over stdio. `setup` registers it; see [MCP server](#mcp-serve
|
|
|
656
672
|
|
|
657
673
|
---
|
|
658
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
|
+
|
|
659
698
|
## Configuration
|
|
660
699
|
|
|
661
700
|
### `.nativ/config.json`
|
|
@@ -907,3 +946,34 @@ npm run sync-plugin # after changing a template or the setup generator; a t
|
|
|
907
946
|
## License
|
|
908
947
|
|
|
909
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,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
|
+
}
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/commands/update.js
CHANGED
|
@@ -1,10 +1,54 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import pc from 'picocolors';
|
|
4
5
|
import { detectProject } from '../scanner/detector.js';
|
|
5
6
|
import { applySetup, getTemplatesDir, planTemplateFile } from '../core/setup-assets.js';
|
|
6
7
|
import { printSetupResult } from './setup.js';
|
|
7
8
|
import { runValidate } from './validate.js';
|
|
9
|
+
const noCr = (text) => text.replace(/\r/g, '');
|
|
10
|
+
function walkFiles(dir) {
|
|
11
|
+
if (!fs.existsSync(dir))
|
|
12
|
+
return [];
|
|
13
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
14
|
+
.flatMap((e) => (e.isDirectory() ? walkFiles(path.join(dir, e.name)) : [path.join(dir, e.name)]));
|
|
15
|
+
}
|
|
16
|
+
// Plain copies (no stamp) so doctor can compare them; edited files are kept unless forced.
|
|
17
|
+
function syncTasteBundle(templatesDir, aiDir, force) {
|
|
18
|
+
const src = path.join(templatesDir, 'design', 'taste');
|
|
19
|
+
let kept = 0;
|
|
20
|
+
const manifestPath = path.join(aiDir, 'design', 'taste', '.manifest.json');
|
|
21
|
+
let manifest = {};
|
|
22
|
+
try {
|
|
23
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
catch { /* installed before manifests */ }
|
|
26
|
+
const hash = (t) => createHash('sha256').update(noCr(t)).digest('hex');
|
|
27
|
+
for (const file of walkFiles(src)) {
|
|
28
|
+
const rel = path.relative(src, file).split(path.sep).join('/');
|
|
29
|
+
const dest = path.join(aiDir, 'design', 'taste', ...rel.split('/'));
|
|
30
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
31
|
+
const existing = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : null;
|
|
32
|
+
const shown = `.ai/design/taste/${rel}`;
|
|
33
|
+
if (existing !== null && noCr(existing) === noCr(text)) {
|
|
34
|
+
printChange({ path: shown, action: 'unchanged' });
|
|
35
|
+
manifest[rel] ??= hash(text);
|
|
36
|
+
}
|
|
37
|
+
else if (existing !== null && !force && manifest[rel] !== hash(existing)) {
|
|
38
|
+
kept++;
|
|
39
|
+
printChange({ path: shown, action: 'skipped', detail: 'differs from the template' });
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
43
|
+
fs.writeFileSync(dest, text, 'utf8');
|
|
44
|
+
manifest[rel] = hash(text);
|
|
45
|
+
printChange({ path: shown, action: existing === null ? 'created' : 'updated' });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
49
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
50
|
+
return kept;
|
|
51
|
+
}
|
|
8
52
|
const ICON = { created: '+', updated: '~', unchanged: '=', skipped: '!' };
|
|
9
53
|
function printChange(change) {
|
|
10
54
|
const paint = change.action === 'created' ? pc.green : change.action === 'updated' ? pc.cyan : change.action === 'skipped' ? pc.yellow : pc.dim;
|
|
@@ -62,6 +106,8 @@ export async function runUpdate(targetDirArg, options = {}) {
|
|
|
62
106
|
kept++;
|
|
63
107
|
printChange({ path: item.rel, action: plan.action, detail: plan.detail });
|
|
64
108
|
}
|
|
109
|
+
console.log(pc.bold('\nDesign bundle'));
|
|
110
|
+
kept += syncTasteBundle(templatesDir, aiDir, options.force);
|
|
65
111
|
// 2. Everything `nativ setup` manages: AGENTS.md, .claude/agents/, settings, MCP server
|
|
66
112
|
console.log(pc.bold('\nClaude Code configuration'));
|
|
67
113
|
const setup = applySetup(targetDir, { force: options.force });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const DESIGN_PROFILES: readonly ["taste-v1", "taste-v2"];
|
|
2
|
+
export type DesignProfile = (typeof DESIGN_PROFILES)[number];
|
|
3
|
+
export interface DesignFinding {
|
|
4
|
+
line: number;
|
|
5
|
+
column: number;
|
|
6
|
+
rule: string;
|
|
7
|
+
severity: 'error' | 'warning';
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
interface Rule {
|
|
11
|
+
id: string;
|
|
12
|
+
profiles: readonly DesignProfile[];
|
|
13
|
+
severity: 'error' | 'warning';
|
|
14
|
+
message: string;
|
|
15
|
+
pattern?: RegExp;
|
|
16
|
+
exts?: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
export declare const DESIGN_RULES: readonly Rule[];
|
|
19
|
+
export declare const SCAN_EXTENSIONS: string[];
|
|
20
|
+
export declare function readProfile(uiSpecsText: string): DesignProfile | null;
|
|
21
|
+
export declare function isCommentLine(line: string): boolean;
|
|
22
|
+
export declare function checkText(text: string, ext: string, profile: DesignProfile): DesignFinding[];
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export const DESIGN_PROFILES = ['taste-v1', 'taste-v2'];
|
|
2
|
+
const TASTE = ['taste-v1', 'taste-v2'];
|
|
3
|
+
export const DESIGN_RULES = [
|
|
4
|
+
{
|
|
5
|
+
id: 'no-emoji', profiles: DESIGN_PROFILES, severity: 'error',
|
|
6
|
+
message: 'Emoji found; use an icon from the catalog',
|
|
7
|
+
pattern: /\p{Extended_Pictographic}/gu,
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: 'no-em-dash', profiles: ['taste-v2'], severity: 'error',
|
|
11
|
+
message: 'Em or en dash found; use a comma, colon or hyphen',
|
|
12
|
+
pattern: /[–—]/g,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'no-h-screen', profiles: TASTE, severity: 'error',
|
|
16
|
+
message: 'h-screen found; use min-h-[100dvh]',
|
|
17
|
+
pattern: /(?<![\w-])h-screen(?![\w-])/g,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'no-scroll-listener', profiles: TASTE, severity: 'error',
|
|
21
|
+
message: 'Scroll listener found; use IntersectionObserver or CSS',
|
|
22
|
+
pattern: /addEventListener\(\s*['"`]scroll['"`]/g,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'no-pure-black', profiles: TASTE, severity: 'warning',
|
|
26
|
+
message: 'Pure black found; use an off-black token',
|
|
27
|
+
pattern: /#000(?:000)?(?![0-9a-fA-F])|rgba?\(\s*0\s*,\s*0\s*,\s*0\s*[,)]/g,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'no-google-fonts-link', profiles: ['taste-v2'], severity: 'warning',
|
|
31
|
+
message: 'Google Fonts link found; self-host the font',
|
|
32
|
+
pattern: /fonts\.googleapis\.com/g,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'no-custom-cursor', profiles: TASTE, severity: 'warning',
|
|
36
|
+
message: 'Custom cursor found',
|
|
37
|
+
pattern: /cursor:\s*url\(|cursor-\[url/g,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'no-hand-rolled-icon', profiles: TASTE, severity: 'warning',
|
|
41
|
+
message: 'Inline svg with a path; use an icon from the catalog',
|
|
42
|
+
exts: ['.tsx', '.jsx'],
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
export const SCAN_EXTENSIONS = [
|
|
46
|
+
'.tsx', '.jsx', '.ts', '.js', '.html', '.vue', '.svelte', '.astro', '.mdx', '.css',
|
|
47
|
+
];
|
|
48
|
+
const PROFILE_LINE = /^- \*\*Profile:\*\*\s*(taste-v1|taste-v2)\s*$/m;
|
|
49
|
+
export function readProfile(uiSpecsText) {
|
|
50
|
+
const m = PROFILE_LINE.exec(uiSpecsText);
|
|
51
|
+
return m ? m[1] : null;
|
|
52
|
+
}
|
|
53
|
+
export function isCommentLine(line) {
|
|
54
|
+
const t = line.trim();
|
|
55
|
+
return ['//', '/*', '*', '{/*', '<!--'].some((p) => t.startsWith(p));
|
|
56
|
+
}
|
|
57
|
+
function active(profile, ext) {
|
|
58
|
+
return DESIGN_RULES.filter((r) => r.profiles.includes(profile) && (!r.exts || r.exts.includes(ext)));
|
|
59
|
+
}
|
|
60
|
+
function lineStarts(text) {
|
|
61
|
+
const starts = [0];
|
|
62
|
+
for (let i = 0; i < text.length; i++)
|
|
63
|
+
if (text[i] === '\n')
|
|
64
|
+
starts.push(i + 1);
|
|
65
|
+
return starts;
|
|
66
|
+
}
|
|
67
|
+
function locate(starts, index) {
|
|
68
|
+
let lo = 0;
|
|
69
|
+
let hi = starts.length - 1;
|
|
70
|
+
while (lo < hi) {
|
|
71
|
+
const mid = (lo + hi + 1) >> 1;
|
|
72
|
+
if (starts[mid] <= index)
|
|
73
|
+
lo = mid;
|
|
74
|
+
else
|
|
75
|
+
hi = mid - 1;
|
|
76
|
+
}
|
|
77
|
+
return { line: lo + 1, column: index - starts[lo] + 1 };
|
|
78
|
+
}
|
|
79
|
+
function svgFindings(text, rule, starts, lines) {
|
|
80
|
+
const out = [];
|
|
81
|
+
const re = /<svg\b/g;
|
|
82
|
+
let m;
|
|
83
|
+
while ((m = re.exec(text))) {
|
|
84
|
+
const end = text.indexOf('</svg>', m.index);
|
|
85
|
+
const body = text.slice(m.index, end === -1 ? text.length : end);
|
|
86
|
+
const pos = locate(starts, m.index);
|
|
87
|
+
if (/<path\b/.test(body) && !isCommentLine(lines[pos.line - 1])) {
|
|
88
|
+
out.push({ ...pos, rule: rule.id, severity: rule.severity, message: rule.message });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
export function checkText(text, ext, profile) {
|
|
94
|
+
const rules = active(profile, ext);
|
|
95
|
+
const lines = text.split('\n');
|
|
96
|
+
const starts = lineStarts(text);
|
|
97
|
+
const found = [];
|
|
98
|
+
for (const rule of rules) {
|
|
99
|
+
if (!rule.pattern) {
|
|
100
|
+
found.push(...svgFindings(text, rule, starts, lines));
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
lines.forEach((line, i) => {
|
|
104
|
+
if (isCommentLine(line))
|
|
105
|
+
return;
|
|
106
|
+
for (const m of line.matchAll(rule.pattern)) {
|
|
107
|
+
found.push({
|
|
108
|
+
line: i + 1, column: (m.index ?? 0) + 1,
|
|
109
|
+
rule: rule.id, severity: rule.severity, message: rule.message,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return found.sort((a, b) => a.line - b.line || a.column - b.column);
|
|
115
|
+
}
|
|
@@ -169,18 +169,37 @@ function checkLearnings(root) {
|
|
|
169
169
|
}
|
|
170
170
|
return findings;
|
|
171
171
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
if (specs === null || /Marketing pages/i.test(specs))
|
|
172
|
+
function checkDesignProfile(specs) {
|
|
173
|
+
if (specs === null || /^##\s+Design Profile\b/m.test(specs))
|
|
175
174
|
return [];
|
|
176
175
|
return [{
|
|
177
|
-
id: 'specs:
|
|
176
|
+
id: 'specs:design-profile',
|
|
178
177
|
status: 'info',
|
|
179
178
|
category: 'specs',
|
|
180
|
-
message: '.ai/ui_specs.md
|
|
181
|
-
+ '
|
|
179
|
+
message: '.ai/ui_specs.md has no Design Profile section. Ask the architect to add it from the '
|
|
180
|
+
+ 'current nativ template (templates/dot-ai/ui_specs.md).',
|
|
182
181
|
}];
|
|
183
182
|
}
|
|
183
|
+
function checkTasteBundle(root, templatesDir) {
|
|
184
|
+
const mk = (message) => [{ id: 'design:taste-bundle', status: 'info', category: 'specs', message }];
|
|
185
|
+
const dir = path.join(root, '.ai', 'design', 'taste');
|
|
186
|
+
if (!fs.existsSync(dir)) {
|
|
187
|
+
return mk('The design bundle .ai/design/taste/ is missing. Run `nativ update` to install it.');
|
|
188
|
+
}
|
|
189
|
+
const differs = ['LICENSE', 'v1/SKILL.md', 'v2/SKILL.md'].filter((rel) => {
|
|
190
|
+
const want = readText(path.join(templatesDir, 'design', 'taste', rel));
|
|
191
|
+
const have = readText(path.join(dir, rel));
|
|
192
|
+
return want !== null && (have === null || have.replace(/\r/g, '') !== want.replace(/\r/g, ''));
|
|
193
|
+
});
|
|
194
|
+
if (!differs.length)
|
|
195
|
+
return [];
|
|
196
|
+
return mk(`Design bundle files differ from the nativ package: ${differs.join(', ')}. `
|
|
197
|
+
+ 'Run `nativ update` (or `nativ update --force` to replace edited ones).');
|
|
198
|
+
}
|
|
199
|
+
function checkUiSpecs(root, templatesDir) {
|
|
200
|
+
const specs = readText(path.join(root, '.ai', 'ui_specs.md'));
|
|
201
|
+
return [...checkDesignProfile(specs), ...checkTasteBundle(root, templatesDir)];
|
|
202
|
+
}
|
|
184
203
|
export function checkHarnessIntegrity(root, options = {}) {
|
|
185
204
|
const findings = [
|
|
186
205
|
...checkGuardrailOverrides(root),
|
|
@@ -188,7 +207,7 @@ export function checkHarnessIntegrity(root, options = {}) {
|
|
|
188
207
|
...checkMissingRoleGuides(root),
|
|
189
208
|
...checkEditedRoleGuides(root, options.templatesDir ?? getTemplatesDir()),
|
|
190
209
|
...checkLearnings(root),
|
|
191
|
-
...checkUiSpecs(root),
|
|
210
|
+
...checkUiSpecs(root, options.templatesDir ?? getTemplatesDir()),
|
|
192
211
|
];
|
|
193
212
|
if (!findings.some((f) => f.category === 'guardrail' || f.category === 'secret')) {
|
|
194
213
|
const message = 'No guardrail overrides and no secrets in files agents read';
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* The project manager's playbook. The SessionStart hook prints it into the main session only:
|
|
3
3
|
* subagents load CLAUDE.md and AGENTS.md on every turn, so anything kept there is paid for by every worker.
|
|
4
4
|
*/
|
|
5
|
-
export declare const PM_PLAYBOOK = "## Working as Project Manager\n\n- Start each session with `nativ task next --json` (or the `nativ_task_next` MCP tool).\n- Delegate, do not implement. Hand each task to the `worker` agent by task id, passing the task's `recommendedModel` (haiku, sonnet or opus) as the Agent tool's `model`; with none, the worker's default applies. Ask the `verifier` agent for an independent check before you mark a milestone done. Ask the `architect` agent for anything that changes a contract.\n- Before the architect designs on existing code, run the `explorer` when `.ai/codebase_map.md` is missing or its commit is well behind `HEAD`, and pass its report to the architect. Give the explorer the current commit (`git rev-parse --short HEAD`); it cannot look it up. Pass `model: haiku` for a single lookup such as \"where is the auth middleware?\".\n- Keep usage low. Run this session on Sonnet. Agent defaults: architect Opus, worker Sonnet, verifier Haiku. Pass `haiku` for mechanical tasks (wording, docs, renames) and `sonnet` for the verifier at the end of a milestone. Never pass a model the human did not choose.\n- Independent tasks can run in parallel in worktrees (`nativ worktree create <taskId>`). On a Claude subscription every agent draws from the same usage limit, so run two or three at a time, not ten.\n- You may not edit `.ai/`. If the enforcement hook warns about a file, that is a scope or contract gap: escalate with `nativ task escalate`, do not edit around it.\n- `nativ doctor` confirms the integration is intact. Run it after updating Claude Code or nativ, and whenever hooks or MCP tools seem to be missing.\n\n## Autonomous execution\n\nWhen told to run autonomously (\"run the queue\", \"execute milestone\", AFK mode):\n1. `nativ_task_next`, then `nativ_task_start`.\n2. Delegate to the `worker` agent with the task id and recommended model.\n3. The worker completes the task itself; `nativ task complete` already ran the check. Do not run `nativ_verify` or `nativ_task_complete` again for it.\n4. Loop to the next task immediately, without asking the human.\n5. Stop only when the milestone is done (ask the `verifier` for a milestone check, then tell the human) or when a task is blocked or escalated (present the four-part summary).\n\n## Talking to the human\n\nThe human is the product owner, not a log reader. For a blocker, trade-off or escalation use four parts in plain words: what the user would see; why it happens, as simple cause and effect; what is affected and what is safe; two or three options with the best marked `(Recommended)`, then one direct question. Say \"the app tried to link a record to an item that does not exist yet\", not \"foreign key constraint violation\". Never paste stack traces; put raw output in a collapsed `<details>` block.";
|
|
5
|
+
export declare const PM_PLAYBOOK = "## Working as Project Manager\n\n- Start each session with `nativ task next --json` (or the `nativ_task_next` MCP tool).\n- Delegate, do not implement. Hand each task to the `worker` agent by task id, passing the task's `recommendedModel` (haiku, sonnet or opus) as the Agent tool's `model`; with none, the worker's default applies. Ask the `verifier` agent for an independent check before you mark a milestone done. Ask the `architect` agent for anything that changes a contract.\n- Before the architect designs on existing code, run the `explorer` when `.ai/codebase_map.md` is missing or its commit is well behind `HEAD`, and pass its report to the architect. Give the explorer the current commit (`git rev-parse --short HEAD`); it cannot look it up. Pass `model: haiku` for a single lookup such as \"where is the auth middleware?\".\n- Keep usage low. Run this session on Sonnet. Agent defaults: architect Opus, worker Sonnet, verifier Haiku. Pass `haiku` for mechanical tasks (wording, docs, renames) and `sonnet` for the verifier at the end of a milestone. Never pass a model the human did not choose.\n- Independent tasks can run in parallel in worktrees (`nativ worktree create <taskId>`). On a Claude subscription every agent draws from the same usage limit, so run two or three at a time, not ten.\n- You may not edit `.ai/`. If the enforcement hook warns about a file, that is a scope or contract gap: escalate with `nativ task escalate`, do not edit around it.\n- `nativ doctor` confirms the integration is intact. Run it after updating Claude Code or nativ, and whenever hooks or MCP tools seem to be missing.\n\n## Autonomous execution\n\nWhen told to run autonomously (\"run the queue\", \"execute milestone\", AFK mode):\n1. `nativ_task_next`, then `nativ_task_start`.\n2. Delegate to the `worker` agent with the task id and recommended model.\n3. The worker completes the task itself; `nativ task complete` already ran the check. Do not run `nativ_verify` or `nativ_task_complete` again for it.\n4. Loop to the next task immediately, without asking the human.\n5. Stop only when the milestone is done (ask the `verifier` for a milestone check, then tell the human) or when a task is blocked or escalated (present the four-part summary).\n\n## Talking to the human\n\nThe human is the product owner, not a log reader. For a blocker, trade-off or escalation use four parts in plain words: what the user would see; why it happens, as simple cause and effect; what is affected and what is safe; two or three options with the best marked `(Recommended)`, plus one line on when the other option would be the better choice, then one direct question. Say \"the app tried to link a record to an item that does not exist yet\", not \"foreign key constraint violation\". Never paste stack traces; put raw output in a collapsed `<details>` block.";
|
|
@@ -23,4 +23,4 @@ When told to run autonomously ("run the queue", "execute milestone", AFK mode):
|
|
|
23
23
|
|
|
24
24
|
## Talking to the human
|
|
25
25
|
|
|
26
|
-
The human is the product owner, not a log reader. For a blocker, trade-off or escalation use four parts in plain words: what the user would see; why it happens, as simple cause and effect; what is affected and what is safe; two or three options with the best marked \`(Recommended)\`, then one direct question. Say "the app tried to link a record to an item that does not exist yet", not "foreign key constraint violation". Never paste stack traces; put raw output in a collapsed \`<details>\` block.`;
|
|
26
|
+
The human is the product owner, not a log reader. For a blocker, trade-off or escalation use four parts in plain words: what the user would see; why it happens, as simple cause and effect; what is affected and what is safe; two or three options with the best marked \`(Recommended)\`, plus one line on when the other option would be the better choice, then one direct question. Say "the app tried to link a record to an item that does not exist yet", not "foreign key constraint violation". Never paste stack traces; put raw output in a collapsed \`<details>\` block.`;
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { runTriage } from './commands/triage.js';
|
|
|
18
18
|
import { runNotifyTest } from './commands/notify.js';
|
|
19
19
|
import { runLearnPropose, runLearnDecide, runLearnList } from './commands/learn.js';
|
|
20
20
|
import { runProfileCommand } from './commands/profile.js';
|
|
21
|
+
import { runDesignCheck } from './commands/design-check.js';
|
|
21
22
|
import { packageVersion } from './core/version.js';
|
|
22
23
|
export function createProgram() {
|
|
23
24
|
const program = new Command();
|
|
@@ -29,6 +30,7 @@ export function createProgram() {
|
|
|
29
30
|
.command('init [targetDir]')
|
|
30
31
|
.description('Scaffold the workflow (.ai/, AGENTS.md, directives) and the Claude Code configuration in the target directory')
|
|
31
32
|
.option('-f, --force', 'Overwrite existing specification and directive files')
|
|
33
|
+
.option('--design <profile>', 'Design profile: nativ, taste-v1 or taste-v2')
|
|
32
34
|
.action(async (targetDir, options) => {
|
|
33
35
|
await runInit(targetDir, options);
|
|
34
36
|
});
|
|
@@ -239,6 +241,15 @@ export function createProgram() {
|
|
|
239
241
|
.action(async (taskId, targetDir, options) => {
|
|
240
242
|
await runTaskUnlock(taskId, targetDir, { reason: options.reason, revoke: options.revoke });
|
|
241
243
|
});
|
|
244
|
+
const design = program.command('design').description('Design profile tools');
|
|
245
|
+
design
|
|
246
|
+
.command('check [paths...]')
|
|
247
|
+
.description('Scan source files for design-rule violations (default path: src)')
|
|
248
|
+
.option('--profile <id>', 'Design profile: nativ, taste-v1 or taste-v2')
|
|
249
|
+
.option('--json', 'Output findings as JSON')
|
|
250
|
+
.action((paths, options) => {
|
|
251
|
+
runDesignCheck(paths, options);
|
|
252
|
+
});
|
|
242
253
|
const learn = program
|
|
243
254
|
.command('learn')
|
|
244
255
|
.description('Project lessons the contracts do not capture: agents propose, a human approves');
|