create-hozu 0.6.0 → 0.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.
Files changed (41) hide show
  1. package/dist/guide.d.ts +29 -0
  2. package/dist/guide.d.ts.map +1 -0
  3. package/dist/guide.js +83 -0
  4. package/dist/guide.js.map +1 -0
  5. package/dist/index.d.ts +12 -3
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +18 -33
  8. package/dist/index.js.map +1 -1
  9. package/package.json +1 -1
  10. package/skill/SKILL.md +39 -92
  11. package/skill/example/{server.ts → app.ts} +10 -9
  12. package/skill/example/features/bookmarks/feature.ts +13 -0
  13. package/skill/example/features/bookmarks/views.ts +2 -95
  14. package/skill/example/hozu.config.ts +4 -1
  15. package/skill/topics/auth.md +10 -6
  16. package/skill/topics/content.md +1 -1
  17. package/skill/topics/contracts.md +8 -5
  18. package/skill/topics/data.md +19 -6
  19. package/skill/topics/deploy.md +13 -9
  20. package/skill/topics/diagnostics.md +28 -11
  21. package/skill/topics/endpoints.md +21 -9
  22. package/skill/topics/feature.md +79 -0
  23. package/skill/topics/forms.md +14 -3
  24. package/skill/topics/http.md +1 -1
  25. package/skill/topics/i18n.md +11 -6
  26. package/skill/topics/machine.md +8 -2
  27. package/skill/topics/pages.md +18 -4
  28. package/skill/topics/patterns.md +34 -4
  29. package/skill/topics/recipes.md +48 -0
  30. package/skill/topics/testing.md +42 -17
  31. package/skill/topics/views.md +12 -7
  32. package/skill/topics/widgets.md +6 -4
  33. package/templates/app/app.ts +7 -0
  34. package/templates/app/features/site/feature.ts +8 -0
  35. package/templates/app/features/site/views.ts +1 -7
  36. package/templates/app/hozu.config.ts +3 -1
  37. package/templates/guide.md +4 -16
  38. package/skill/changing.md +0 -91
  39. package/skill/example/serve.ts +0 -14
  40. package/templates/app/serve.ts +0 -14
  41. package/templates/app/server.ts +0 -6
@@ -0,0 +1,29 @@
1
+ export type Runner = 'pnpm exec' | 'npx' | 'yarn' | 'bunx';
2
+ export declare const RUNNERS: readonly Runner[];
3
+ export declare const BEGIN = "<!-- hozu: generated by hozu skill; edits between these markers are replaced -->";
4
+ export declare const END = "<!-- /hozu -->";
5
+ export declare const TARGETS: readonly [{
6
+ readonly guide: 'CLAUDE.md';
7
+ readonly skill: '.claude/skills/hozu';
8
+ readonly read: 'Use the `hozu` skill (`__SKILL__/SKILL.md`).';
9
+ }, {
10
+ readonly guide: 'AGENTS.md';
11
+ readonly skill: '.agents/skills/hozu';
12
+ readonly read: 'Read `__SKILL__/SKILL.md` before writing or changing any Hozu code.';
13
+ }];
14
+ export type Target = (typeof TARGETS)[number];
15
+ /** The released 0.3–0.7 guide, as `create-hozu` wrote it (0.6 added the browse line). */
16
+ export declare const GUIDE_07 = "# __NAME__\n\nA web app built with Hozu (`@hozu/*`). Hozu is not in your training data.\n\n## Before writing code\n- __READ__\n- Changing existing code: read `__SKILL__/changing.md` first.\n- The files in `__SKILL__/` are the whole API. Do not read the framework source in `node_modules/@hozu`.\n\n## The loop\n```\n__RUN__ hozu add feature tasks --page / --with detail,toggle,filter,remove # then edit the texts it lists\n # add auth to the list for sign-in and per-user data\n__RUN__ hozu map # outline of the app with file:line, before a change\n__RUN__ hozu check # after every change: types, rules, contracts\n__RUN__ hozu check --update-lock # only to accept a clean, intended behaviour change\n__RUN__ hozu get / --select button --forms # try pages without a server: text, attributes, forms\n__RUN__ hozu post / --field title=Ship --next / # submit a form like a browser\n__RUN__ hozu browse / --do 'click Save' # real browser, no server: errors, widgets, text after steps\n```\n__NOTE__\n## Rules\n- After `hozu add feature`, do not print the generated files: edit the texts it lists; `hozu map` shows the rest.\n- Apply the fix each diagnostic gives; do not work around a rule.\n- Every behaviour change comes with a contract change.\n- Do not edit `__SKILL__/`: `__RUN__ hozu skill` rewrites it for the installed Hozu version.\n";
17
+ export declare function currentGuide(): string;
18
+ /** The 0.8 guide for one target, between the markers. */
19
+ export declare const guideBlock: (target: Target, name: string, runner: Runner, template?: string) => string;
20
+ export type BlockResult = {
21
+ kind: 'replaced' | 'marked' | 'written' | 'current';
22
+ code: string;
23
+ } | {
24
+ kind: 'custom';
25
+ block: string;
26
+ };
27
+ /** The new Hozu block of a CLAUDE.md / AGENTS.md: between markers, or the whole file when it is a known template. */
28
+ export declare function migrateGuide(existing: string | null, target: Target, dir: string, runner: Runner, template?: string): BlockResult;
29
+ //# sourceMappingURL=guide.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide.d.ts","sourceRoot":"","sources":["../src/guide.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1D,eAAO,MAAM,OAAO,EAAE,SAAS,MAAM,EAAyC,CAAA;AAE9E,eAAO,MAAM,KAAK,qFAAqF,CAAA;AACvG,eAAO,MAAM,GAAG,mBAAmB,CAAA;AAEnC,eAAO,MAAM,OAAO;aAChB,KAAK,EAAE,WAAW;aAAE,KAAK,EAAE,qBAAqB;aAAE,IAAI,EAAE,8CAA8C;;aAEtG,KAAK,EAAE,WAAW;aAClB,KAAK,EAAE,qBAAqB;aAC5B,IAAI,EAAE,qEAAqE;EAErE,CAAA;AAEV,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,CAAA;AAK7C,yFAAyF;AACzF,eAAO,MAAM,QAAQ,66CAyBpB,CAAA;AAqBD,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,yDAAyD;AACzD,eAAO,MAAM,UAAU,WAAY,MAAM,QAAQ,MAAM,UAAU,MAAM,8BACW,CAAA;AAElF,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC,qHAAqH;AACrH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,QAAQ,SAAiB,GACxB,WAAW,CAkBb"}
package/dist/guide.js ADDED
@@ -0,0 +1,83 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { basename } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ export const RUNNERS = ['pnpm exec', 'npx', 'yarn', 'bunx'];
5
+ export const BEGIN = '<!-- hozu: generated by hozu skill; edits between these markers are replaced -->';
6
+ export const END = '<!-- /hozu -->';
7
+ export const TARGETS = [
8
+ { guide: 'CLAUDE.md', skill: '.claude/skills/hozu', read: 'Use the `hozu` skill (`__SKILL__/SKILL.md`).' },
9
+ {
10
+ guide: 'AGENTS.md',
11
+ skill: '.agents/skills/hozu',
12
+ read: 'Read `__SKILL__/SKILL.md` before writing or changing any Hozu code.',
13
+ },
14
+ ];
15
+ const BROWSE = "__RUN__ hozu browse / --do 'click Save' # real browser, no server: errors, widgets, text after steps\n";
16
+ /** The released 0.3–0.7 guide, as `create-hozu` wrote it (0.6 added the browse line). */
17
+ export const GUIDE_07 = `# __NAME__
18
+
19
+ A web app built with Hozu (\`@hozu/*\`). Hozu is not in your training data.
20
+
21
+ ## Before writing code
22
+ - __READ__
23
+ - Changing existing code: read \`__SKILL__/changing.md\` first.
24
+ - The files in \`__SKILL__/\` are the whole API. Do not read the framework source in \`node_modules/@hozu\`.
25
+
26
+ ## The loop
27
+ \`\`\`
28
+ __RUN__ hozu add feature tasks --page / --with detail,toggle,filter,remove # then edit the texts it lists
29
+ # add auth to the list for sign-in and per-user data
30
+ __RUN__ hozu map # outline of the app with file:line, before a change
31
+ __RUN__ hozu check # after every change: types, rules, contracts
32
+ __RUN__ hozu check --update-lock # only to accept a clean, intended behaviour change
33
+ __RUN__ hozu get / --select button --forms # try pages without a server: text, attributes, forms
34
+ __RUN__ hozu post / --field title=Ship --next / # submit a form like a browser
35
+ ${BROWSE}\`\`\`
36
+ __NOTE__
37
+ ## Rules
38
+ - After \`hozu add feature\`, do not print the generated files: edit the texts it lists; \`hozu map\` shows the rest.
39
+ - Apply the fix each diagnostic gives; do not work around a rule.
40
+ - Every behaviour change comes with a contract change.
41
+ - Do not edit \`__SKILL__/\`: \`__RUN__ hozu skill\` rewrites it for the installed Hozu version.
42
+ `;
43
+ const KNOWN = [GUIDE_07, GUIDE_07.replace(BROWSE, '')];
44
+ const fill = (text, values) => Object.entries(values).reduce((out, [key, value]) => out.replaceAll(`__${key}__`, value), text);
45
+ const note = (runner, skillRunner) => runner === skillRunner
46
+ ? ''
47
+ : `\nThe skill writes commands as \`${skillRunner} …\`; in this app use \`${runner} …\`.\n`;
48
+ function filled(template, target, name, runner, skillRunner) {
49
+ return fill(fill(template, { READ: target.read }), {
50
+ NAME: name,
51
+ RUN: runner,
52
+ SKILL: target.skill,
53
+ NOTE: note(runner, skillRunner),
54
+ });
55
+ }
56
+ export function currentGuide() {
57
+ return readFileSync(fileURLToPath(new URL('../templates/guide.md', import.meta.url)), 'utf8');
58
+ }
59
+ /** The 0.8 guide for one target, between the markers. */
60
+ export const guideBlock = (target, name, runner, template = currentGuide()) => `${BEGIN}\n${filled(template, target, name, runner, 'npx').trimEnd()}\n${END}\n`;
61
+ /** The new Hozu block of a CLAUDE.md / AGENTS.md: between markers, or the whole file when it is a known template. */
62
+ export function migrateGuide(existing, target, dir, runner, template = currentGuide()) {
63
+ const begin = existing?.indexOf(BEGIN) ?? -1;
64
+ const end = existing?.indexOf(END) ?? -1;
65
+ const own = begin >= 0 && end > begin ? existing.slice(begin, end) : existing;
66
+ const name = own?.match(/^# (.+)$/m)?.[1] ?? basename(dir);
67
+ const detected = RUNNERS.find((r) => own?.includes(`${r} hozu `)) ?? runner;
68
+ const block = guideBlock(target, name, detected, template);
69
+ if (existing === null)
70
+ return { kind: 'written', code: block };
71
+ if (begin >= 0 && end > begin) {
72
+ const code = existing.slice(0, begin) + block + existing.slice(end + END.length).replace(/^\n/, '');
73
+ return { kind: code === existing ? 'current' : 'replaced', code };
74
+ }
75
+ for (const r of RUNNERS) {
76
+ if (KNOWN.some((known) => existing === filled(known, target, name, r, 'pnpm exec')))
77
+ return { kind: 'marked', code: block };
78
+ if (existing === filled(template, target, name, r, 'npx'))
79
+ return { kind: 'marked', code: block };
80
+ }
81
+ return { kind: 'custom', block };
82
+ }
83
+ //# sourceMappingURL=guide.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide.js","sourceRoot":"","sources":["../src/guide.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import { type Runner } from './guide.ts';
1
2
  export type Agent = 'claude' | 'agents' | 'both';
2
- export type Runner = 'pnpm exec' | 'npx' | 'yarn' | 'bunx';
3
+ export type { BlockResult, Runner, Target } from './guide.ts';
4
+ export { BEGIN, currentGuide, END, GUIDE_07, guideBlock, migrateGuide, RUNNERS, TARGETS } from './guide.ts';
3
5
  export declare const AGENTS: readonly Agent[];
4
6
  export declare const defaultSkill: string;
5
7
  export declare const runnerOf: (userAgent: string | undefined) => Runner;
@@ -8,7 +10,14 @@ export interface AgentOptions {
8
10
  runner: Runner;
9
11
  skillSource?: string;
10
12
  }
11
- export declare function writeAgentFiles(dir: string, agent: Agent, options: AgentOptions): Promise<string[]>;
13
+ export interface AgentFiles {
14
+ written: string[];
15
+ custom: {
16
+ guide: string;
17
+ block: string;
18
+ }[];
19
+ }
20
+ export declare function writeAgentFiles(dir: string, agent: Agent, options: AgentOptions): Promise<AgentFiles>;
12
21
  export interface CreateOptions extends AgentOptions {
13
22
  agent: Agent;
14
23
  version: string;
@@ -24,6 +33,7 @@ export declare const packageJson: (name: string, version: string) => {
24
33
  };
25
34
  dependencies: {
26
35
  '@hozu/adapter-node': string;
36
+ '@hozu/cli': string;
27
37
  '@hozu/core': string;
28
38
  '@hozu/css': string;
29
39
  '@hozu/data': string;
@@ -33,7 +43,6 @@ export declare const packageJson: (name: string, version: string) => {
33
43
  zod: string;
34
44
  };
35
45
  devDependencies: {
36
- '@hozu/cli': string;
37
46
  '@hozu/testing': string;
38
47
  '@types/node': string;
39
48
  typescript: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAChD,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1D,eAAO,MAAM,MAAM,EAAE,SAAS,KAAK,EAAiC,CAAA;AAGpE,eAAO,MAAM,YAAY,QAAsB,CAAA;AAiB/C,eAAO,MAAM,QAAQ,cAAe,MAAM,GAAG,SAAS,KAAG,MAGxD,CAAA;AAWD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAwBzG;AAYD,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,WAAW,SAAU,MAAM,WAAW,MAAM;;;;;QAKrD,KAAK;QACL,KAAK;QACL,KAAK;;;QAGL,oBAAoB;QACpB,YAAY;QACZ,WAAW;QACX,YAAY;QACZ,sBAAsB;QACtB,kBAAkB;QAClB,iBAAiB;QACjB,GAAG;;;QAGH,WAAW;QACX,eAAe;QACf,aAAa;QACb,UAAU;;;QAED,IAAI;;CACf,CAAA;AAEF,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBtF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,MAAM,EAAW,MAAM,YAAY,CAAA;AAE/D,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAChD,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3G,eAAO,MAAM,MAAM,EAAE,SAAS,KAAK,EAAiC,CAAA;AAGpE,eAAO,MAAM,YAAY,QAAsB,CAAA;AAM/C,eAAO,MAAM,QAAQ,cAAe,MAAM,GAAG,SAAS,KAAG,MAGxD,CAAA;AAQD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC3C;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAkB3G;AAYD,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,WAAW,SAAU,MAAM,WAAW,MAAM;;;;;QAKrD,KAAK;QACL,KAAK;QACL,KAAK;;;QAGL,oBAAoB;QACpB,WAAW;QACX,YAAY;QACZ,WAAW;QACX,YAAY;QACZ,sBAAsB;QACtB,kBAAkB;QAClB,iBAAiB;QACjB,GAAG;;;QAGH,eAAe;QACf,aAAa;QACb,UAAU;;;QAED,IAAI;;CACf,CAAA;AAEF,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBtF"}
package/dist/index.js CHANGED
@@ -1,52 +1,37 @@
1
1
  import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join, relative } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+ import { migrateGuide, TARGETS } from './guide.js';
5
+ export { BEGIN, currentGuide, END, GUIDE_07, guideBlock, migrateGuide, RUNNERS, TARGETS } from './guide.js';
4
6
  export const AGENTS = ['claude', 'agents', 'both'];
5
7
  const root = fileURLToPath(new URL('../', import.meta.url));
6
8
  export const defaultSkill = join(root, 'skill');
7
9
  const templates = join(root, 'templates');
8
- const targets = (agent) => [
9
- {
10
- guide: 'CLAUDE.md',
11
- skill: '.claude/skills/hozu',
12
- read: 'Use the `hozu` skill (`__SKILL__/SKILL.md`).',
13
- },
14
- {
15
- guide: 'AGENTS.md',
16
- skill: '.agents/skills/hozu',
17
- read: 'Read `__SKILL__/SKILL.md` before writing or changing any Hozu code.',
18
- },
19
- ].filter((_, i) => agent === 'both' || (agent === 'claude' ? i === 0 : i === 1));
10
+ const targets = (agent) => TARGETS.filter((_, i) => agent === 'both' || (agent === 'claude' ? i === 0 : i === 1));
20
11
  export const runnerOf = (userAgent) => {
21
12
  const name = userAgent?.split('/')[0];
22
13
  return name === 'npm' ? 'npx' : name === 'yarn' ? 'yarn' : name === 'bun' ? 'bunx' : 'pnpm exec';
23
14
  };
24
15
  const exists = (path) => stat(path).then(() => true, () => false);
25
- const fill = (text, values) => Object.entries(values).reduce((out, [key, value]) => out.replaceAll(`__${key}__`, value), text);
26
16
  export async function writeAgentFiles(dir, agent, options) {
27
- const written = [];
28
- const guide = await readFile(join(templates, 'guide.md'), 'utf8');
17
+ const out = { written: [], custom: [] };
29
18
  for (const t of targets(agent)) {
30
19
  const skill = join(dir, t.skill);
31
20
  await rm(skill, { recursive: true, force: true });
32
21
  await mkdir(dirname(skill), { recursive: true });
33
22
  await cp(options.skillSource ?? defaultSkill, skill, { recursive: true });
34
- written.push(t.skill);
23
+ out.written.push(t.skill);
35
24
  const file = join(dir, t.guide);
36
- if (await exists(file))
37
- continue;
38
- const values = {
39
- NAME: options.name,
40
- RUN: options.runner,
41
- SKILL: t.skill,
42
- NOTE: options.runner === 'pnpm exec'
43
- ? ''
44
- : `\nThe skill writes commands as \`pnpm exec …\`; in this app use \`${options.runner} …\`.\n`,
45
- };
46
- await writeFile(file, fill(fill(guide, { READ: t.read }), values));
47
- written.push(t.guide);
25
+ const existing = (await exists(file)) ? await readFile(file, 'utf8') : null;
26
+ const result = migrateGuide(existing, t, dir, options.runner);
27
+ if (result.kind === 'custom')
28
+ out.custom.push({ guide: t.guide, block: result.block });
29
+ else if (result.kind !== 'current') {
30
+ await writeFile(file, result.code);
31
+ out.written.push(t.guide);
32
+ }
48
33
  }
49
- return written;
34
+ return out;
50
35
  }
51
36
  async function files(dir) {
52
37
  const out = [];
@@ -65,11 +50,12 @@ export const packageJson = (name, version) => ({
65
50
  type: 'module',
66
51
  scripts: {
67
52
  check: 'hozu check',
68
- start: 'node --import @hozu/transform/register serve.ts',
53
+ start: 'hozu serve',
69
54
  build: 'hozu build',
70
55
  },
71
56
  dependencies: {
72
57
  '@hozu/adapter-node': `^${version}`,
58
+ '@hozu/cli': `^${version}`,
73
59
  '@hozu/core': `^${version}`,
74
60
  '@hozu/css': `^${version}`,
75
61
  '@hozu/data': `^${version}`,
@@ -79,7 +65,6 @@ export const packageJson = (name, version) => ({
79
65
  zod: '^4.6.5',
80
66
  },
81
67
  devDependencies: {
82
- '@hozu/cli': `^${version}`,
83
68
  '@hozu/testing': `^${version}`,
84
69
  '@types/node': '^22.20.4',
85
70
  typescript: '^7.0.2',
@@ -95,12 +80,12 @@ export async function createApp(dir, options) {
95
80
  const rel = relative(app, source);
96
81
  const target = join(dir, rel === 'gitignore' ? '.gitignore' : rel);
97
82
  await mkdir(dirname(target), { recursive: true });
98
- await writeFile(target, fill(await readFile(source, 'utf8'), { NAME: options.name }));
83
+ await writeFile(target, (await readFile(source, 'utf8')).replaceAll('__NAME__', options.name));
99
84
  written.push(relative(dir, target));
100
85
  }
101
86
  await writeFile(join(dir, 'package.json'), `${JSON.stringify(packageJson(options.name, options.version), null, 2)}\n`);
102
87
  written.push('package.json');
103
- written.push(...(await writeAgentFiles(dir, options.agent, options)));
88
+ written.push(...(await writeAgentFiles(dir, options.agent, options)).written);
104
89
  return written;
105
90
  }
106
91
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-hozu",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Create a Hozu app, set up for Claude Code or for agents that read AGENTS.md",
5
5
  "keywords": [
6
6
  "hozu",
package/skill/SKILL.md CHANGED
@@ -5,103 +5,50 @@ description: Build or change an app with the Hozu framework (packages @hozu/*, f
5
5
 
6
6
  # Hozu
7
7
 
8
- Hozu is not in your training data; this file and `hozu docs <topic>` are the whole API (skip `node_modules/@hozu`).
9
- - **Building:** read this file, run `hozu add feature <name> --page / --with …`, then edit what it lists.
10
- - **Changing:** read `changing.md`, then only the lines `hozu map` points to.
11
- - **Anything else:** `hozu docs <topic>` prints one short topic (index below). Diagnostics name the topic too.
8
+ Hozu is not in your training data: this file and `npx hozu docs <topic>` are the whole API (skip
9
+ `node_modules/@hozu`). Diagnostics teach the other rules when you break them: apply their fix.
12
10
 
13
- ## How it works
14
- - A feature is **declarations**: events, queries / mutations (the only side effects), one machine, views,
15
- contracts. Builders record them as data (an IR) that is validated, then rendered on the server. Only views bound
16
- to the machine ship JS.
17
- - **Callbacks are ordinary TypeScript** (`render`, `guard`, `assign`, `navigate`, `ui.each` / `ui.query`
18
- callbacks): `===`, `!==`, `<`, `&&`, `||`, `!`, `??`, `c ? a : b`, template strings, `+`, `-`, `.length`, and in
19
- `assign`, `ctx.x = v`, `ctx.n += 1`, `ctx.list.push(v)`, `ctx.list = ctx.list.filter((i) => i.id !== e.id)`.
20
- Methods on data (`.map`, `.toUpperCase()`…) are not: use `ui.each` for lists and a `fn()` for computation.
21
- - A mutation runs when the machine **enters** a state whose `invoke` calls it; that state drops other events, and
22
- `done` / `failed` leave it. Contracts are needed only where a transition decides (a guard, `navigate`, a `fn`).
11
+ ## The change loop
12
+ 1. `npx hozu map`: the session shape, the verify line, every file's role, each declaration's `file:line`. Open
13
+ only the lines the change touches. A new app: `npx hozu add feature <name> --page / --with …`, then
14
+ `npx hozu docs feature`.
15
+ 2. Edit everything the change needs (table below).
16
+ 3. `npx hozu check` once. For an intended behaviour change, `npx hozu check --update-lock`, then list the accepted
17
+ `now:` lines in your summary.
18
+ 4. Verify once with the line `hozu map` prints: `npx hozu browse <path> --session '…' --js both --do '…'`. It runs
19
+ the same app as `npm start`, with and without JS; do not start a server or use `curl`.
23
20
 
24
- ## Files and commands
25
- ```
26
- hozu.config.ts project({ schema, site, routes, pages, features }) routes.ts route() declarations
27
- features/<name>/model.ts schemas, events, effects, machine views.ts views, contracts, feature()
28
- features/<name>/server.ts implement(...) resolvers serve.ts createServer(...)
29
- ```
30
- ```
31
- npx hozu check # after every edit: types, rules, contracts
32
- npx hozu check --update-lock # accept an intended behaviour change
33
- npx hozu add feature items --page / --with auth,detail,toggle,filter,remove
34
- npx hozu map # outline with file:line
35
- npx hozu get / --select button --forms # a page, no server needed
36
- npx hozu post / --field title=A --next / # a no-JS form post, cookies shown
37
- npx hozu browse / --do 'fill Search=a' --do 'click Save' # real browser: errors, widgets, text
38
- npx hozu docs views # one topic
39
- ```
40
- `get` / `post` / `browse` replace a running server for checks; `npm start` runs the app. Relative imports end in `.ts`.
21
+ ## What to touch
22
+ | Change | Touch |
23
+ |---|---|
24
+ | UI-only state (a tab) | model: a context field, an event, an `on` whose `assign` sets it → views: the control |
25
+ | Filter / sort in the URL | the route's `search` (with a default) → `ui.link(route, params, { key })` → `search.key`; filtering while typing: `seed: ({ search }) => ({ key: search.key })`, read `ctx.key` |
26
+ | A per-item action stored on the server (pin, archive) | model: the item field, an event, a mutation that `invalidates` the list tag, `on(E, { target: 'pinning', assign: (e) => { ctx.target = e.id } })`, a state with `invoke` → views: the per-item form (`hozu docs patterns`) → app: store it, sort in the list resolver |
27
+ | A control every state handles | `machine({ on: [...] })` |
28
+ | New page | `routes.ts` → a view with `route` → `ui.page(...)` in `hozu.config.ts` |
41
29
 
42
- ## A feature in one screen
43
- ```ts
44
- // model.ts
45
- export const Item = z.object({ id: z.string(), title: z.string(), done: z.boolean() })
46
- export const Add = event({ payload: z.object({ title: z.string() }) })
47
- export const itemsTag = tag({ param: null })
48
- export const listItems = query({ input: z.object({}), output: z.array(Item), scope: 'public',
49
- freshness: 'static', tags: () => [itemsTag()] })
50
- export const addItem = mutation({ input: z.object({ title: z.string().min(2, 'Too short') }), output: Item,
51
- errors: { Duplicate: z.object({ title: z.string() }) }, invalidates: () => [itemsTag()] })
52
- export const items = machine({
53
- context: z.object({ draft: z.string(), error: z.string().nullable() }),
54
- initialContext: { draft: '', error: null },
55
- initial: 'idle',
56
- states: ({ ctx }) => ({
57
- idle: { on: [on(Add, { target: 'adding', assign: (e) => { ctx.draft = e.title; ctx.error = null } })] },
58
- adding: {
59
- invoke: invoke(addItem, {
60
- input: { title: ctx.draft },
61
- done: { target: 'idle', assign: () => { ctx.draft = '' } },
62
- failed: {
63
- Duplicate: { target: 'idle', assign: () => { ctx.error = 'Already listed' } },
64
- Unexpected: { target: 'idle', assign: (e) => { ctx.error = e.message } },
65
- },
66
- }),
67
- },
68
- }),
69
- })
70
- // views.ts
71
- export const Board = ui.view({
72
- machine: items,
73
- render: ({ ctx, when }) =>
74
- ui.main({ class: 'mx-auto max-w-xl' }, [
75
- ui.form({ on: { submit: ui.send(Add, { title: ui.dom.form('title') }) } }, [
76
- ui.input({ name: 'title', required: true, value: ctx.draft }),
77
- ui.button({ type: 'submit' }, ['Add']),
78
- ]),
79
- ctx.error !== null && ui.p({ role: 'alert' }, [ctx.error]),
80
- when(['adding'], [ui.p({ 'aria-busy': 'true' }, [`Adding ${ctx.draft}…`])]),
81
- ui.query(listItems, {}, {
82
- ready: (list) => ui.ul({}, [ui.each(list, 'id', (i) => ui.li({}, [i.title, i.done ? ' ✓' : '']))]),
83
- failed: { Unexpected: () => ui.p({ role: 'alert' }, ['Unavailable']) },
84
- }),
85
- ]),
86
- })
87
- export const todos = feature({ id: 'todos', intent: { summary: 'A to-do list' },
88
- declarations: { Add, itemsTag, listItems, addItem, items, Board } })
89
- ```
90
- Every declaration goes in `declarations` once, under its name. Resolvers:
91
- `implement(addItem, ({ title }, { fail }) => exists ? fail('Duplicate', { title }) : save(title))`.
30
+ ## Rules no diagnostic checks
31
+ - **Query resolvers only read.** Writes belong in mutation and endpoint resolvers; a prefetched link runs queries.
32
+ - **Other users, reloads, sign-out:** verify them in one `browse` chain with `--js both` (`--as <name>` per user).
33
+ - **Contracts only where a transition decides:** a guard, a `navigate`, a `fn` or computed value. Copy-only
34
+ transitions are reviewed in the lock.
35
+ - **`'live'` only for push** to pages that are already open.
36
+ - **`invalidates` drives the client refresh** of every query with those tags, whatever its freshness.
92
37
 
93
- ## Topics (`hozu docs <topic>`)
38
+ ## Topics (`npx hozu docs <topic>`)
94
39
  | Task | Topic |
95
40
  |---|---|
96
- | elements, attributes, classes, events, DOM fields, lists, links | `views` |
97
- | states, events, invoke, timers, guards, updates | `machine` |
98
- | queries, mutations, tags, errors, `fn()`, resolvers | `data` |
99
- | writing and checking contracts | `contracts` |
100
- | routes, params, search, pages, `head`, 404, sitemap | `pages` |
101
- | forms without JS, field errors, selects | `forms` |
41
+ | a new feature, the files, a complete example | `feature` |
42
+ | elements, attributes, events, lists, links, reuse | `views` |
43
+ | states, events, invoke, timers, guards | `machine` |
44
+ | queries, mutations, tags, `fn()`, resolvers | `data` |
45
+ | contracts and the lock | `contracts` |
46
+ | routes, search, pages, `head`, 403 / 404 | `pages` |
47
+ | forms, checkboxes, bulk forms, no JS | `forms` |
102
48
  | sign-in, sessions, per-user data | `auth` |
103
- | common UI: filters, empty states, per-item actions, load more, optimistic | `patterns` |
104
- | webhooks and JSON APIs | `endpoints` |
105
- | browser APIs and DOM libraries (maps, charts) | `widgets` |
106
- | languages, env, HTTP, Markdown, images, preview, PWA, tests, deployment | `i18n`, `env`, `http`, `content`, `testing`, `deploy` |
49
+ | filters, modes, per-item actions, load more | `patterns` |
50
+ | worked changes | `recipes` |
51
+ | webhooks, JSON APIs, redirects | `endpoints` |
52
+ | browser APIs, DOM libraries | `widgets` |
53
+ | languages, env, HTTP, Markdown, tests, deploying | `i18n` `env` `http` `content` `testing` `deploy` |
107
54
  | a diagnostic code | `diagnostics` |
@@ -1,16 +1,17 @@
1
1
  import { resolvers } from '@hozu/data'
2
+ import { app } from '@hozu/runtime-server'
2
3
  import { addBookmark, getBookmark, listBookmarks, toggleRead } from './features/bookmarks/model.ts'
3
4
  import project from './hozu.config.ts'
4
5
 
5
6
  type Kind = 'article' | 'video' | 'podcast'
6
7
 
7
- export function createResolvers() {
8
- const items = [
9
- { id: 'b1', title: 'Closed-world UI', kind: 'article' as Kind, read: false },
10
- { id: 'b2', title: 'Islands explained', kind: 'video' as Kind, read: true },
11
- ]
12
- let seq = items.length
13
- return resolvers(project, (implement) => [
8
+ const items = [
9
+ { id: 'b1', title: 'Closed-world UI', kind: 'article' as Kind, read: false },
10
+ { id: 'b2', title: 'Islands explained', kind: 'video' as Kind, read: true },
11
+ ]
12
+ let seq = items.length
13
+ export default app({
14
+ resolvers: resolvers(project, (implement) => [
14
15
  implement(listBookmarks, () => items.map((b) => ({ ...b }))),
15
16
  implement(getBookmark, ({ id }, { fail }) => {
16
17
  const b = items.find((x) => x.id === id)
@@ -30,5 +31,5 @@ export function createResolvers() {
30
31
  b.read = !b.read
31
32
  return { ...b }
32
33
  }),
33
- ])
34
- }
34
+ ]),
35
+ })
@@ -0,0 +1,13 @@
1
+ import { feature } from '@hozu/core'
2
+ import * as model from './model.ts'
3
+ import * as views from './views.ts'
4
+
5
+ export const bookmarks = feature({
6
+ id: 'bookmarks',
7
+ intent: {
8
+ summary:
9
+ 'A shared reading list: add bookmarks with a kind, mark them read, filter unread, one page each.',
10
+ invariants: ['Titles are unique, case-insensitive', 'New bookmarks are listed first'],
11
+ },
12
+ declarations: [model, views],
13
+ })