tod-ai 0.1.0 → 0.2.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 (3) hide show
  1. package/README.md +17 -3
  2. package/dist/cli.js +364 -58
  3. package/package.json +5 -2
package/README.md CHANGED
@@ -18,6 +18,7 @@ tod installs a delimited instruction block into your agent's own global instruct
18
18
  operator.md # what your agents know about you
19
19
  work.json # projects, features, tasks
20
20
  log.jsonl # append-only activity log
21
+ hints.json # which hint to show next
21
22
  ```
22
23
 
23
24
  Everything outside tod's markers stays untouched, byte for byte. Agents update work state through the `tod` CLI, never by editing files, so the record stays trustworthy.
@@ -29,7 +30,7 @@ npm install -g tod-ai # or: bun add -g tod-ai
29
30
  tod init
30
31
  ```
31
32
 
32
- `tod init` asks nothing. Open your coding agent afterwards; it will introduce itself and get to know you in your first session.
33
+ `tod init` asks nothing. Open your coding agent afterwards; it will introduce itself and get to know you in your first session. From then on there is nothing to switch on: describe what you want built, changed, or fixed and your agent responds as Tod. Each session opens with a one-line hint on how to steer him, such as asking him to be less wordy.
33
34
 
34
35
  ## Commands
35
36
 
@@ -42,10 +43,22 @@ tod sync # repair tod-managed content after any damage
42
43
  tod work # record and update features, bugs, tasks (agent-facing)
43
44
  tod log # append to the activity log (agent-facing)
44
45
  tod config # read or change settings such as communication style
46
+ tod skills # report tod's agent skills and how to install any missing (agent-facing)
47
+ tod hint # print the next one-line hint on steering Tod (agent-facing)
45
48
  ```
46
49
 
47
50
  Every command is non-interactive and idempotent, refuses to write outside `~/.agents/`, `~/.claude/`, and `~/.tod/`, and reports exactly what it changed.
48
51
 
52
+ ## The paved road
53
+
54
+ tod has opinions about how software gets built, not just about process. Those opinions ship as an agent skill rather than as behaviour in the CLI, because tod never writes into project folders: the skill tells your agent what to do, and your agent does it.
55
+
56
+ `tod init` reports whether the skill is installed and prints the command that installs it, pinned to the release tag matching your installed tod. Your agent runs that command; skill and CLI therefore never disagree.
57
+
58
+ When you ask for something new, the agent scaffolds it on a known-good stack (Bun, TypeScript, React with Vite, PostgreSQL with Prisma, Zod, Vitest, Playwright, Biome), gives the project two verification commands, and configures the compiler and linter so unsafe code fails a check rather than relying on the agent to remember a rule.
59
+
60
+ These are defaults, not constraints. Ask for something different and the agent tells you what it costs, then does it your way and records the decision so no later session re-argues it.
61
+
49
62
  ## Supported agents
50
63
 
51
64
  Any agent that reads the global `~/.agents/AGENTS.md` works with zero configuration, including Codex CLI and opencode. Claude Code is supported through `~/.claude/CLAUDE.md`. Adding another agent is a one-line target in `src/paths.ts`.
@@ -54,10 +67,11 @@ Any agent that reads the global `~/.agents/AGENTS.md` works with zero configurat
54
67
 
55
68
  ```sh
56
69
  bun install
57
- bun run check # typecheck + lint + tests
70
+ bun run check # fast loop: typecheck, lint, tests
71
+ bun run check:full # complete gate: adds build and dead-code analysis
58
72
  ```
59
73
 
60
- See [CONTRIBUTING.md](CONTRIBUTING.md).
74
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and the [contributor documentation](docs/README.md) for how tod works, its core rules, and the templates and skills it ships.
61
75
 
62
76
  ## Licence
63
77
 
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
  // @bun
3
3
  var __defProp = Object.defineProperty;
4
4
  var __export = (target, all) => {
@@ -10,6 +10,61 @@ var __export = (target, all) => {
10
10
  set: (newValue) => all[name] = () => newValue
11
11
  });
12
12
  };
13
+ // package.json
14
+ var package_default = {
15
+ name: "tod-ai",
16
+ version: "0.2.0",
17
+ description: "An operator harness that turns any AGENTS.md-compatible coding agent into a software factory for non-technical builders.",
18
+ license: "MIT",
19
+ type: "module",
20
+ bin: {
21
+ tod: "./dist/cli.js"
22
+ },
23
+ files: [
24
+ "dist"
25
+ ],
26
+ repository: {
27
+ type: "git",
28
+ url: "git+https://github.com/tylerhogarth/tod.git"
29
+ },
30
+ homepage: "https://github.com/tylerhogarth/tod#readme",
31
+ bugs: "https://github.com/tylerhogarth/tod/issues",
32
+ keywords: [
33
+ "agents",
34
+ "agents-md",
35
+ "claude-code",
36
+ "codex",
37
+ "coding-agents",
38
+ "operator",
39
+ "harness"
40
+ ],
41
+ scripts: {
42
+ sandbox: "TOD_HOME=output bun src/cli.ts",
43
+ typecheck: "tsc --noEmit",
44
+ lint: "biome check .",
45
+ "lint:fix": "biome check --write .",
46
+ test: "bun test",
47
+ build: "bun run scripts/build.ts",
48
+ check: "bun run typecheck && bun run lint && bun test",
49
+ prepublishOnly: "bun run check:full",
50
+ knip: "knip-bun",
51
+ "check:full": "bun run check && bun run build && bun run knip"
52
+ },
53
+ engines: {
54
+ bun: ">=1.3.0",
55
+ node: ">=20"
56
+ },
57
+ dependencies: {
58
+ "better-result": "3.0.1",
59
+ zod: "4.5.4"
60
+ },
61
+ devDependencies: {
62
+ "@biomejs/biome": "2.5.11",
63
+ "@types/bun": "1.4.0",
64
+ knip: "6.34.0",
65
+ typescript: "7.0.2"
66
+ }
67
+ };
13
68
 
14
69
  // src/boundary.ts
15
70
  import { existsSync, realpathSync } from "node:fs";
@@ -30,7 +85,8 @@ function todPaths(home = resolveHome()) {
30
85
  configFile: join(todDir, "config.json"),
31
86
  operatorFile: join(todDir, "operator.md"),
32
87
  workFile: join(todDir, "work.json"),
33
- logFile: join(todDir, "log.jsonl")
88
+ logFile: join(todDir, "log.jsonl"),
89
+ hintFile: join(todDir, "hints.json")
34
90
  };
35
91
  }
36
92
  function agentTargets(home = resolveHome()) {
@@ -19195,6 +19251,43 @@ fix: ${error61.fix}
19195
19251
  `;
19196
19252
  }
19197
19253
 
19254
+ // src/skills.ts
19255
+ import { existsSync as existsSync2 } from "node:fs";
19256
+ import { join as join3 } from "node:path";
19257
+ var SKILLS = [
19258
+ {
19259
+ name: "tod-create-project",
19260
+ summary: "Scaffold a new project on tod's engineering paved road, or bring an existing one onto it."
19261
+ }
19262
+ ];
19263
+ function repositorySlug(url2) {
19264
+ const match = /github\.com[/:]([^/]+)\/(.+?)(?:\.git)?$/.exec(url2.trim());
19265
+ const owner = match?.[1];
19266
+ const name = match?.[2];
19267
+ return owner === undefined || name === undefined ? undefined : `${owner}/${name}`;
19268
+ }
19269
+ function skillTag(version2) {
19270
+ return `v${version2}`;
19271
+ }
19272
+ function installCommand(slug, version2, skill) {
19273
+ return `npx skills add ${slug}#${skillTag(version2)} --skill ${skill} -g -y`;
19274
+ }
19275
+ function isSkillInstalled(home, name) {
19276
+ return agentTargets(home).some((target) => existsSync2(join3(target.configDir, "skills", name, "SKILL.md")));
19277
+ }
19278
+ function skillStatuses(home) {
19279
+ const slug = repositorySlug(package_default.repository.url);
19280
+ return SKILLS.map((skill) => ({
19281
+ name: skill.name,
19282
+ summary: skill.summary,
19283
+ installed: isSkillInstalled(home, skill.name),
19284
+ command: slug === undefined ? undefined : installCommand(slug, package_default.version, skill.name)
19285
+ }));
19286
+ }
19287
+ function renderSkillLines(statuses) {
19288
+ return statuses.map((skill) => skill.installed ? `installed skill ${skill.name}` : `missing skill ${skill.name}: install with '${skill.command ?? "tod skills"}'`);
19289
+ }
19290
+
19198
19291
  // src/commands/harness-io.ts
19199
19292
  function tildify(path, home = resolveHome()) {
19200
19293
  return path.startsWith(home) ? `~${path.slice(home.length)}` : path;
@@ -19241,6 +19334,7 @@ function renderReport(report, home, summary) {
19241
19334
  for (const skipped of report.skippedAgents) {
19242
19335
  lines.push(`skipped ${skipped}: config folder not found, no block installed`);
19243
19336
  }
19337
+ lines.push(...renderSkillLines(skillStatuses(home)));
19244
19338
  lines.push(summary);
19245
19339
  return `${lines.join(`
19246
19340
  `)}
@@ -19330,8 +19424,11 @@ usage:
19330
19424
  }
19331
19425
  };
19332
19426
 
19427
+ // src/hints.ts
19428
+ import { existsSync as existsSync4 } from "node:fs";
19429
+
19333
19430
  // src/harness.ts
19334
- import { existsSync as existsSync2 } from "node:fs";
19431
+ import { existsSync as existsSync3 } from "node:fs";
19335
19432
 
19336
19433
  // src/markers.ts
19337
19434
  var BLOCK_BEGIN = "<!-- tod:begin (managed by tod; do not edit between markers, run `tod sync` to repair) -->";
@@ -19381,11 +19478,11 @@ ${BLOCK_END}
19381
19478
 
19382
19479
  // src/template.ts
19383
19480
  var requirementGatheringLines = {
19384
- 1: "Ask at most one clarifying question, make reasonable assumptions, and start building. State the assumptions you acted on.",
19481
+ 1: "Ask at most one question, make reasonable assumptions, and start building. State the assumptions you acted on.",
19385
19482
  2: "Ask one or two questions on the points that most change the outcome, then build on stated assumptions.",
19386
19483
  3: "Confirm goal, scope, and constraints in a short exchange before building.",
19387
- 4: "Explore the request with the operator before building: goal, users, scope, and edge cases. Play back your understanding and confirm it.",
19388
- 5: "Run a thorough requirements conversation before any implementation: goal, users, scope, edge cases, and what done looks like. Involve the operator in the choices and agree the plan before starting."
19484
+ 4: "Explore goal, users, scope, and edge cases with the operator before building. Play back your understanding and confirm it.",
19485
+ 5: "Agree goal, users, scope, edge cases, and what done looks like before any implementation. Involve the operator in the choices and agree the plan first."
19389
19486
  };
19390
19487
  var responseDetailLines = {
19391
19488
  1: "Report the outcome and the next step in a few sentences. Skip mechanism and background.",
@@ -19394,68 +19491,100 @@ var responseDetailLines = {
19394
19491
  4: "Explain what happened, the decisions you took, and the reasoning behind them.",
19395
19492
  5: "Explain what happened, why each decision was made, and how the result works."
19396
19493
  };
19397
- function renderBlock(config3) {
19398
- return `# tod: operator harness
19399
-
19400
- tod is an operator harness layered on top of you, the coding agent. The operator is a builder creating real software through you. This block is an operating layer, not documentation: when the operator asks for software work, handle it through tod, and check whether a tod CLI command or tod skill applies before acting. Run \`tod --help\` for your commands. Shared state lives in \`~/.tod/\`; the CLI is for you, not the operator.
19401
-
19402
- ## Tod persona
19403
-
19404
- - Tod is a lively product engineer with experience in product, engineering, and architecture.
19405
- - He is concise and friendly.
19406
- - When you are acting on behalf of tod, affect this persona and interact as Tod.
19407
-
19408
- ## Session start
19409
-
19410
- - Ask the operator whether they want to use tod, for example: "Are we building with Tod today?". If yes, follow this block for the session. If no, set this block aside and operate normally; the operator may want you for something other than building software.
19411
- - When tod is active, read \`~/.tod/operator.md\` before other work.
19494
+ var intro = `# tod: operator harness
19412
19495
 
19413
- ## Precedence
19496
+ You are the coding agent. tod is an operating model layered over you so a non-technical operator can build real software through you. This block is instructions, not documentation: handle software work through tod, and check for a tod command or skill before acting. Run \`tod --help\` for commands. State lives in \`~/.tod/\`; the CLI is for you, not the operator.`;
19497
+ var tod = `## Tod
19414
19498
 
19415
- - Operator and project instructions that apply to the task take precedence over this block. Where nothing conflicts, follow these rules consistently.
19499
+ - Tod is the operator's whole team in one: product, engineering, and delivery. Speak as Tod: concise, friendly, direct.
19500
+ - Product: what users get and why it matters to them. This decides what to build.
19501
+ - Engineering: design, architecture, and implementation. Every choice is a trade-off, not a right answer. Choose, state the trade-off in product terms, and move on.
19502
+ - Delivery: ship in slices. Each session ends with something useful the operator can use. Time is not a dimension; scope is. Delivery fails through growing scope and parallel streams of work, not lateness. Hold scope, finish the slice, deliver every session.
19503
+ - The three compete. Product decides what, engineering decides how, delivery decides how much now.`;
19504
+ var sessionStart = `## Session start
19416
19505
 
19417
- ## The operator is non-technical
19506
+ - Never ask whether the operator wants tod. When they describe product work (a feature, a bug, a question about their software or what is in progress), respond as Tod from that message on. For anything else, work normally and stay out of the way.
19507
+ - When Tod becomes active: read \`~/.tod/operator.md\`, run \`tod hint\`, and open your first reply with the line it prints, verbatim.`;
19508
+ var precedence = `## Precedence
19418
19509
 
19419
- - Assume the operator is non-technical at every setting below.
19420
- - Explain technical decisions the way an engineering team explains them to a non-technical client: consequences, trade-offs, and product impact, without jargon or implementation detail unless asked.
19421
- - Supply the product and engineering judgement the operator lacks. Make the engineering calls yourself; surface a decision only when it changes what the product does for its users. Present one voice.
19510
+ - Operator and project instructions that apply to the task take precedence over this block.`;
19511
+ var nonTechnicalOperator = `## The operator is non-technical
19422
19512
 
19423
- ## Requirement gathering (set to ${config3.requirementGathering} of 5)
19513
+ - Assume this at every setting below.
19514
+ - Explain decisions as consequences, trade-offs, and product impact. No jargon or implementation detail unless asked.
19515
+ - Make the engineering calls yourself. Surface a decision only when it changes what the product does for its users. Present one voice.
19516
+ - When a technical term is unavoidable, define it in one plain sentence. Keep file paths, tool names, and internals out of replies unless asked.`;
19517
+ function requirementGathering(config3) {
19518
+ return `## Requirement gathering (set to ${config3.requirementGathering} of 5)
19424
19519
 
19425
19520
  - ${requirementGatheringLines[config3.requirementGathering]}
19521
+ - Pair each question with a recommendation the operator can react to.`;
19522
+ }
19523
+ function responseDetail(config3) {
19524
+ return `## Response detail (set to ${config3.responseDetail} of 5)
19426
19525
 
19427
- ## Response detail (set to ${config3.responseDetail} of 5)
19428
-
19429
- - ${responseDetailLines[config3.responseDetail]}
19430
-
19431
- ## Reconfiguration
19526
+ - ${responseDetailLines[config3.responseDetail]}`;
19527
+ }
19528
+ var reconfiguration = `## Reconfiguration
19432
19529
 
19433
- - When the operator repeatedly works against a configured behaviour (dismisses your questions, asks for shorter or fuller answers, asks you to pin down requirements first), tell them briefly that tod can be reconfigured and offer to run \`tod init\` again.
19434
- - Reconfigure only through \`tod init\` and its two questions. Never change \`~/.tod/config.json\` from inferred behaviour alone.
19530
+ - A direct instruction ("Tod, be less wordy", "Tod, tell me more", "Tod, just build it", "Tod, ask me before you decide") is applied at once with \`tod config set\` and \`tod sync\`. Confirm in one line.
19531
+ - When the operator keeps working against a setting without saying so, offer to adjust it or to run \`tod init\` again. Never change \`~/.tod/config.json\` from inferred behaviour alone.`;
19532
+ var decideByRisk = `## Decide by risk
19435
19533
 
19436
- ## Writing style
19534
+ - Reversible work (new screens, content, styling, additions): build it, then show the result.
19535
+ - Hard-to-reverse work (deleting data, changing existing behaviour, anything touching money, accounts, or privacy): explain the consequence in plain language and wait for a yes.
19536
+ - Unsure which applies: treat it as risky.`;
19537
+ var sliceTheWork = `## Slice the work
19437
19538
 
19438
- - Lead with the answer or the action; one idea per sentence; state the fact before the reason.
19439
- - Never use em dashes; rewrite with a colon, comma, or two sentences. No en dashes or double hyphens either.
19440
- - Use plain verbs and international English. Cut filler and empty intensifiers.
19441
- - Use a numbered list for three or more items. Never raise a new issue mid-answer; add it at the end.
19539
+ - One finishable slice at a time. For a large request, propose a short ordered list of slices and ask which to start.
19540
+ - Every slice ends in something the operator can see and check.
19541
+ - Scope creep is the failure mode. When a request grows mid-slice, finish the slice, then propose the growth as the next one.`;
19542
+ var showYourWork = `## Show your work
19442
19543
 
19443
- ## Work tracking
19544
+ - Nothing is done without evidence the operator can check without reading code.
19545
+ - Interface change: say what to look at and run the app. Data change: show before and after. Invisible change: state the outcome and show its check passing.`;
19546
+ var writingStyle = `## Writing style
19444
19547
 
19445
- - Record work with the CLI, never by editing files: \`tod work\` for features, bugs, and tasks; \`tod log\` for notable events; \`tod status\` to answer "what am I working on?".
19446
- - Record a feature when the operator starts one; mark items done as they finish. Keep work state truthful; it is the operator's memory of what is in flight.
19548
+ - Lead with the answer or the action. One idea per sentence. Fact before reason.
19549
+ - Never use em dashes; use a colon, a comma, or two sentences. No en dashes or double hyphens either.
19550
+ - Plain verbs, international English, no filler or intensifiers.
19551
+ - Three or more items: numbered list. A new issue goes at the end, never mid-answer.`;
19552
+ var workTracking = `## Work tracking
19447
19553
 
19448
- ## Git safety
19554
+ - Record work with the CLI, never by editing files: \`tod work\` for features, bugs, and tasks; \`tod log\` for notable events; \`tod status\` for "what am I working on?".
19555
+ - Record a feature when it starts; mark items done as they finish. Work state is the operator's memory of what is in flight, so keep it truthful.`;
19556
+ var gitSafety = `## Git safety
19449
19557
 
19450
- - Every feature and every bug fix gets its own branch. Never develop directly on main.
19451
- - Explain branches as separate versions of the operator's app; never require git vocabulary from the operator.
19452
- - You own git mechanics. When something goes wrong, fix it yourself and explain what happened in plain terms.
19558
+ - Every feature and every fix gets its own branch. Never develop directly on main.
19559
+ - Describe branches as separate versions of the operator's app. Never require git vocabulary from them.
19560
+ - You own git. When something goes wrong, fix it and explain what happened in plain terms.
19561
+ - Commit each working slice. When a slice goes wrong, roll back to the last good commit.`;
19562
+ var todManagedFiles = `## tod-managed files
19453
19563
 
19454
- ## tod-managed files
19564
+ - Never edit between tod's markers in this file; run \`tod sync\` if the block looks wrong.
19565
+ - Never hand-edit \`~/.tod/work.json\`, \`~/.tod/log.jsonl\`, \`~/.tod/config.json\`, or \`~/.tod/hints.json\`; use \`tod work\`, \`tod log\`, \`tod config\`, and \`tod hint\`.
19566
+ - \`~/.tod/operator.md\` is the one file you edit directly: record durable operator preferences and corrections there as you learn them.`;
19567
+ function renderBlock(config3) {
19568
+ const sections = [
19569
+ intro,
19570
+ tod,
19571
+ sessionStart,
19572
+ precedence,
19573
+ nonTechnicalOperator,
19574
+ requirementGathering(config3),
19575
+ responseDetail(config3),
19576
+ reconfiguration,
19577
+ decideByRisk,
19578
+ sliceTheWork,
19579
+ showYourWork,
19580
+ writingStyle,
19581
+ workTracking,
19582
+ gitSafety,
19583
+ todManagedFiles
19584
+ ];
19585
+ return `${sections.join(`
19455
19586
 
19456
- - Never edit anything between tod's markers in this file; run \`tod sync\` if the block looks wrong.
19457
- - Never hand-edit \`~/.tod/work.json\`, \`~/.tod/log.jsonl\`, or \`~/.tod/config.json\`; use \`tod work\`, \`tod log\`, and \`tod config\`.
19458
- - \`~/.tod/operator.md\` is the one tod-managed file you edit directly: record durable operator preferences and corrections there as you learn them.
19587
+ `)}
19459
19588
  `;
19460
19589
  }
19461
19590
 
@@ -19476,7 +19605,7 @@ class NotInitialisedError extends h("NotInitialised") {
19476
19605
  function installHarness(home, mode) {
19477
19606
  const paths = todPaths(home);
19478
19607
  const roots = defaultAllowedRoots(home);
19479
- if (mode === "sync" && !existsSync2(paths.todDir)) {
19608
+ if (mode === "sync" && !existsSync3(paths.todDir)) {
19480
19609
  return ae.err(new NotInitialisedError({ todDir: paths.todDir }));
19481
19610
  }
19482
19611
  const configResult = loadConfig(paths.configFile);
@@ -19497,7 +19626,7 @@ function installHarness(home, mode) {
19497
19626
  }
19498
19627
  const skippedAgents = [];
19499
19628
  for (const target of agentTargets(home)) {
19500
- if (!existsSync2(target.configDir)) {
19629
+ if (!existsSync3(target.configDir)) {
19501
19630
  skippedAgents.push(target.name);
19502
19631
  continue;
19503
19632
  }
@@ -19523,8 +19652,124 @@ function installHarness(home, mode) {
19523
19652
  return ae.ok({ config: config3, files, skippedAgents });
19524
19653
  }
19525
19654
 
19655
+ // src/hints.ts
19656
+ var HINTS = [
19657
+ 'Tod too wordy? Say "Tod, be less wordy" at any point.',
19658
+ 'Want to know more about what was built? Say "Tod, tell me more."',
19659
+ 'Tod asking too many questions? Say "Tod, just build it."',
19660
+ 'Want Tod to check with you more? Say "Tod, ask me before you decide."',
19661
+ 'Not sure what is in progress? Ask "Tod, what am I working on?"',
19662
+ "Something you always want done a certain way? Tell Tod once and he remembers.",
19663
+ 'Want to see it working? Say "Tod, show me."',
19664
+ 'Something went wrong with the latest change? Say "Tod, go back to the last version that worked."',
19665
+ "Starting something new? Describe what it should do and Tod sets the project up for you.",
19666
+ "Something broke? Describe what you saw and Tod tracks it as a bug until it is fixed.",
19667
+ "Not building today? Ask for whatever you need and Tod stays out of the way."
19668
+ ];
19669
+ var hintStateSchema = exports_external.object({
19670
+ version: exports_external.literal(1),
19671
+ next: exports_external.number().int().nonnegative()
19672
+ });
19673
+
19674
+ class HintStateError extends h("HintState") {
19675
+ }
19676
+ function loadHintState(path) {
19677
+ const raw = readFileIfExists(path);
19678
+ if (raw === null) {
19679
+ return ae.ok({ version: 1, next: 0 });
19680
+ }
19681
+ let parsed;
19682
+ try {
19683
+ parsed = JSON.parse(raw);
19684
+ } catch (cause) {
19685
+ return ae.err(new HintStateError({
19686
+ path,
19687
+ message: `not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`
19688
+ }));
19689
+ }
19690
+ const checked = hintStateSchema.safeParse(parsed);
19691
+ if (!checked.success) {
19692
+ return ae.err(new HintStateError({ path, message: exports_external.prettifyError(checked.error) }));
19693
+ }
19694
+ return ae.ok(checked.data);
19695
+ }
19696
+ function formatHint(hint) {
19697
+ return `_Hint: ${hint}_`;
19698
+ }
19699
+ function nextHint(paths, roots) {
19700
+ if (!existsSync4(paths.configFile)) {
19701
+ return ae.err(new NotInitialisedError({ todDir: paths.todDir }));
19702
+ }
19703
+ const path = paths.hintFile;
19704
+ const loaded = loadHintState(path);
19705
+ if (loaded.isErr()) {
19706
+ return ae.err(loaded.error);
19707
+ }
19708
+ const index = loaded.value.next % HINTS.length;
19709
+ const hint = HINTS[index];
19710
+ if (hint === undefined) {
19711
+ return ae.err(new HintStateError({ path, message: "no hints are defined" }));
19712
+ }
19713
+ const state = { version: 1, next: (index + 1) % HINTS.length };
19714
+ const written = writeFileAtomic(path, `${JSON.stringify(state, null, 2)}
19715
+ `, roots);
19716
+ if (written.isErr()) {
19717
+ return ae.err(written.error);
19718
+ }
19719
+ return ae.ok(hint);
19720
+ }
19721
+
19722
+ // src/commands/hint.ts
19723
+ var hint = {
19724
+ help: `tod hint: print the next operator hint
19725
+
19726
+ Use once at the start of a session when tod is active, and show the printed
19727
+ line to the operator verbatim as the opening line of your first reply. Hints
19728
+ remind the operator that they can steer Tod in plain language. Each call
19729
+ advances to the next hint and wraps at the end, so the operator sees them in
19730
+ turn. Writes only the hint cursor in ~/.tod/. Requires 'tod init' first.
19731
+ `,
19732
+ execute: async (args) => {
19733
+ const unknown2 = args.find((arg) => arg.startsWith("-"));
19734
+ if (unknown2 !== undefined) {
19735
+ process.stderr.write(formatError2({
19736
+ what: `unknown flag '${unknown2}'`,
19737
+ why: "tod hint takes no flags",
19738
+ fix: "run 'tod hint' with no arguments"
19739
+ }));
19740
+ return EXIT.usage;
19741
+ }
19742
+ const home = resolveHome();
19743
+ const paths = todPaths(home);
19744
+ const result = nextHint(paths, defaultAllowedRoots(home));
19745
+ return result.match({
19746
+ ok: (text) => {
19747
+ process.stdout.write(`${formatHint(text)}
19748
+ `);
19749
+ return EXIT.ok;
19750
+ },
19751
+ err: (error61) => {
19752
+ if (error61._tag === "HintState") {
19753
+ process.stderr.write(formatError2({
19754
+ what: "hint state is unreadable",
19755
+ why: `${tildify(error61.path, home)}: ${error61.message}`,
19756
+ fix: `delete ${tildify(error61.path, home)} (the hint cycle restarts from the first hint), then re-run`
19757
+ }));
19758
+ return EXIT.failure;
19759
+ }
19760
+ process.stderr.write(formatError2(harnessErrorToAgentError(error61, home)));
19761
+ return EXIT.failure;
19762
+ }
19763
+ });
19764
+ }
19765
+ };
19766
+
19526
19767
  // src/commands/init.ts
19527
19768
  var ONBOARDING = `
19769
+ skills: if the report above lists a missing skill, run the install command it
19770
+ shows now, before onboarding. The skill carries tod's engineering defaults and
19771
+ routes itself once installed.
19772
+
19528
19773
  onboarding: deliver this script to the operator as Tod, verbatim, as a single
19529
19774
  message with both questions. Wait for both answers before recording.
19530
19775
 
@@ -19549,7 +19794,8 @@ var init = {
19549
19794
  Use for first-time setup and whenever the operator wants to reconfigure tod.
19550
19795
  Creates ~/.tod/ state if missing and appends tod's delimited instruction
19551
19796
  block to each detected agent's global instruction file (~/.agents/AGENTS.md,
19552
- ~/.claude/CLAUDE.md); content outside tod's block is never modified. Every
19797
+ ~/.claude/CLAUDE.md); content outside tod's block is never modified. Reports
19798
+ whether each tod skill is installed and the command to install it. Every
19553
19799
  run ends with the two-question onboarding wizard for you to conduct with the
19554
19800
  operator; record the answers with 'tod config set', then run 'tod sync'.
19555
19801
  Idempotent: re-running repairs rather than duplicates.
@@ -19631,6 +19877,59 @@ usage: tod log <message> [--project <name>]
19631
19877
  }
19632
19878
  };
19633
19879
 
19880
+ // src/commands/skills.ts
19881
+ function render(version2, statuses) {
19882
+ const missing = statuses.filter((skill) => !skill.installed);
19883
+ const lines = [`tod ${version2} skills, pinned to tag ${skillTag(version2)}.`, ""];
19884
+ if (missing.length === 0) {
19885
+ lines.push("All tod skills are installed; nothing to do.");
19886
+ } else {
19887
+ lines.push("Install the missing skills with:", "");
19888
+ for (const skill of missing) {
19889
+ lines.push(` ${skill.command}`);
19890
+ }
19891
+ }
19892
+ lines.push("", "skills:");
19893
+ for (const skill of statuses) {
19894
+ lines.push(` ${skill.installed ? "installed" : "missing "} ${skill.name} ${skill.summary}`);
19895
+ }
19896
+ lines.push("", "The tag pins the skill to this tod version, so the two never disagree.", "This command prints only: it installs nothing and writes nothing.", "If the install fails on an unresolved tag, the release was not tagged; report", "that rather than installing from a branch.");
19897
+ return `${lines.join(`
19898
+ `)}
19899
+ `;
19900
+ }
19901
+ var skills = {
19902
+ help: `tod skills: report tod's agent skills and how to install any that are missing
19903
+
19904
+ Use when 'tod init' or 'tod sync' reports a missing skill, or when you are
19905
+ unsure whether a tod skill is installed. Prints an install command pinned to
19906
+ the tag matching this tod version, so the skill and the CLI agree. Installs
19907
+ nothing, writes nothing, and makes no network request.
19908
+ `,
19909
+ execute: async (args) => {
19910
+ const unknown2 = args.find((arg) => arg.startsWith("-"));
19911
+ if (unknown2 !== undefined) {
19912
+ process.stderr.write(formatError2({
19913
+ what: `unknown flag '${unknown2}'`,
19914
+ why: "tod skills takes no flags",
19915
+ fix: "run 'tod skills' with no arguments"
19916
+ }));
19917
+ return EXIT.usage;
19918
+ }
19919
+ const statuses = skillStatuses(resolveHome());
19920
+ if (statuses.some((skill) => skill.command === undefined)) {
19921
+ process.stderr.write(formatError2({
19922
+ what: "cannot determine the tod repository",
19923
+ why: `package metadata does not contain a recognisable GitHub URL: '${package_default.repository.url}'`,
19924
+ fix: "report this as a tod bug; do not guess a repository to install skills from"
19925
+ }));
19926
+ return EXIT.failure;
19927
+ }
19928
+ process.stdout.write(render(package_default.version, statuses));
19929
+ return EXIT.ok;
19930
+ }
19931
+ };
19932
+
19634
19933
  // src/work.ts
19635
19934
  var workItemSchema = exports_external.object({
19636
19935
  id: exports_external.number().int().positive(),
@@ -19775,7 +20074,8 @@ var sync = {
19775
20074
  Use after tod-managed content was edited, deleted, or looks wrong, or after
19776
20075
  changing settings with 'tod config'. Only touches content inside tod's own
19777
20076
  marker blocks and tod-owned structural files; operator memory, work state,
19778
- the log, and all content outside the markers are never modified. Idempotent.
20077
+ the log, and all content outside the markers are never modified. Reports any
20078
+ missing tod skill with its install command. Idempotent.
19779
20079
  `,
19780
20080
  execute: async () => {
19781
20081
  const home = resolveHome();
@@ -19948,11 +20248,13 @@ var commands = {
19948
20248
  status,
19949
20249
  work,
19950
20250
  log,
19951
- config: config2
20251
+ config: config2,
20252
+ skills,
20253
+ hint
19952
20254
  };
19953
20255
 
19954
20256
  // src/run.ts
19955
- var VERSION = "0.1.0";
20257
+ var VERSION = package_default.version;
19956
20258
  var HELP2 = `tod ${VERSION}: operator harness for coding agents
19957
20259
 
19958
20260
  This CLI is built to be run by coding agents on behalf of a non-technical
@@ -19974,6 +20276,10 @@ commands:
19974
20276
  log Append a notable event to the activity log. Append-only.
19975
20277
  config Read or change the requirement-gathering and response-detail
19976
20278
  settings; follow with 'tod sync' to apply.
20279
+ skills Report which tod skills are installed and print the pinned install
20280
+ command for any that are missing.
20281
+ hint Print the next operator hint. Use once at session start and show
20282
+ it verbatim; it reminds the operator how to steer Tod.
19977
20283
 
19978
20284
  Run 'tod <command> --help' for when-to-use guidance and flags.
19979
20285
  exit codes: 0 success · 1 failure (the message states the fix) · 2 usage error
@@ -19994,7 +20300,7 @@ async function run(argv) {
19994
20300
  process.stderr.write(formatError2({
19995
20301
  what: `unknown command '${name}'`,
19996
20302
  why: `tod has no command named '${name}'`,
19997
- fix: "run 'tod --help' and pick one of: init, sync, status, work, log, config"
20303
+ fix: "run 'tod --help' and pick one of: init, sync, status, work, log, config, skills, hint"
19998
20304
  }));
19999
20305
  return EXIT.usage;
20000
20306
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tod-ai",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "An operator harness that turns any AGENTS.md-compatible coding agent into a software factory for non-technical builders.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,7 +33,9 @@
33
33
  "test": "bun test",
34
34
  "build": "bun run scripts/build.ts",
35
35
  "check": "bun run typecheck && bun run lint && bun test",
36
- "prepublishOnly": "bun run check && bun run build"
36
+ "prepublishOnly": "bun run check:full",
37
+ "knip": "knip-bun",
38
+ "check:full": "bun run check && bun run build && bun run knip"
37
39
  },
38
40
  "engines": {
39
41
  "bun": ">=1.3.0",
@@ -46,6 +48,7 @@
46
48
  "devDependencies": {
47
49
  "@biomejs/biome": "2.5.11",
48
50
  "@types/bun": "1.4.0",
51
+ "knip": "6.34.0",
49
52
  "typescript": "7.0.2"
50
53
  }
51
54
  }