peer-ai 1.0.0-next.0 → 1.0.0-next.1

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/README.md CHANGED
@@ -27,6 +27,7 @@ npm install --save-dev peer-ai
27
27
  | Command | What it does |
28
28
  |---------|--------------|
29
29
  | [`peer-ai init`](#peer-ai-init) | Sets up Peer AI in a repository: one config file, from what it detects |
30
+ | [`peer-ai migrate`](#peer-ai-migrate) | Moves a project from its copy of v0 onto Peer AI 1.0 |
30
31
  | [`peer-ai assess`](#peer-ai-assess) | Maps what the project has and what its stage still needs |
31
32
  | [`peer-ai render`](#peer-ai-render) | Connects each AI tool: instructions, the MCP server and the skills |
32
33
  | [`peer-ai doctor`](#peer-ai-doctor) | Checks the setup and says how to fix what isn't right |
@@ -76,6 +77,40 @@ It never overwrites an existing `peer-ai.config.json`, and it never assumes anyt
76
77
 
77
78
  `0` success, `1` refused (a config already exists) or cancelled, `2` a usage error.
78
79
 
80
+ ## `peer-ai migrate`
81
+
82
+ Moves a project from v0, when Peer AI was a playbook copied into a `peer-ai/` folder, onto the package ([RFC 0008](../../rfcs/0008-moving-a-v0-project-onto-1-0.md)).
83
+
84
+ ```bash
85
+ npx peer-ai migrate --dry-run
86
+ npx peer-ai migrate
87
+ ```
88
+
89
+ It converts what it can read with certainty:
90
+
91
+ | From v0 | Into |
92
+ |---------|------|
93
+ | The Project settings table in the workflow driver | `commands.verify`, `tracker`, `repo.branchNaming`, `repo.mergePolicy` and `design.reference` |
94
+ | `phase-config.json` | `models`, each skill's add-ons and notes, and each activity's notes. A phase that says a part is dormant marks it `dormant`. |
95
+ | Markdown files in `docs/standards/` | `standards.documents`, as standards or an addendum, each scoped to its part |
96
+ | `.peer-ai-state.json` | A work item for each ticket in progress. The tracker keeps the rest. |
97
+
98
+ Then it deletes the `peer-ai/` folder and the state file, takes v0's text out of `CLAUDE.md`, `AGENTS.md` and the other instruction files, maps the project and sets up the AI tools, as `assess` and `render` do.
99
+
100
+ Nothing is dropped silently. Whatever needs judgement, it copies word for word into `docs/peer-ai-migration.md`: the files the project edited in `peer-ai/`, the text it took out of the instruction files, the settings it couldn't place, and anything it left alone that still names v0. A work item, `migrate-v0`, points there, so your AI tool brings each decision up in the next session.
101
+
102
+ It tells the files the project changed from v0's own by their fingerprints: a list of every file in every v0 version, which ships with the package.
103
+
104
+ It never commits. It won't start with uncommitted changes, so the migration is a change of its own: review it, commit it on a branch, and open a pull request. `git restore . && git clean -fd` undoes it all.
105
+
106
+ ### Options
107
+
108
+ The same as `init`. With `--dry-run`, it prints what it would convert, rewrite, delete and leave for a decision, and changes nothing.
109
+
110
+ ### Exit codes
111
+
112
+ `0` success, `1` refused (a config already exists, there is no v0 copy, the project isn't in git or has uncommitted changes) or cancelled, `2` a usage error.
113
+
79
114
  ## `peer-ai assess`
80
115
 
81
116
  Maps what a project already has, and what its stage still needs. Run it on any project, at any point: a brief with no code, a prototype, or a product in production with no documents at all.
@@ -165,7 +200,7 @@ npx peer-ai render
165
200
 
166
201
  `AGENTS.md` gets the block whenever a tool other than Claude Code is listed, when it already exists, or when `CLAUDE.md` imports it.
167
202
 
168
- The instructions are short: how to work through the MCP server, the project's parts, its commands, its compliance packs and its own rules. The server serves the detail when it's needed, rather than every rule on every turn.
203
+ The instructions are short: how to work through the MCP server, the project's parts, its commands, its compliance packs and its own rules, and the project's settings for models, skills and activities when it has any: the models to use, the add-ons, checklists and notes for each skill, and the files to read and notes for each activity. The server serves the detail when it's needed, rather than every rule on every turn.
169
204
 
170
205
  ### Skills
171
206
 
@@ -308,6 +343,15 @@ Run it after the project's own checks:
308
343
  - run: npx peer-ai check
309
344
  ```
310
345
 
346
+ That uses the version in the project's `package.json`. A project without one, such as a Python or Flutter project, needs Node 24 on the runner and the version `render` pinned:
347
+
348
+ ```yaml
349
+ - uses: actions/setup-node@v4
350
+ with:
351
+ node-version: 24
352
+ - run: npx -y peer-ai@<version> check
353
+ ```
354
+
311
355
  ### Options
312
356
 
313
357
  | Option | What it does |
package/dist/cli.js CHANGED
@@ -13,6 +13,7 @@ import { runDoctor } from "./doctor.js";
13
13
  import { runFeedback } from "./feedback.js";
14
14
  import { serveStdio } from "./mcp.js";
15
15
  import { runInit } from "./init.js";
16
+ import { runMigrate } from "./migrate.js";
16
17
  import { VERSION } from "./package-info.js";
17
18
  import { createTerminalPrompter } from "./prompter.js";
18
19
  import { formatReport } from "./report.js";
@@ -23,6 +24,7 @@ const HELP = `peer-ai: from a brief to a shipped product, with any AI tool
23
24
 
24
25
  Usage:
25
26
  peer-ai init [options] Set up Peer AI in this repository
27
+ peer-ai migrate [options] Move a project from its copy of v0 onto Peer AI 1.0
26
28
  peer-ai assess [options] Map what the project has and what its stage still needs
27
29
  peer-ai render [options] Set up each AI tool in the config: instructions and the MCP server
28
30
  peer-ai doctor [options] Check that Peer AI is set up correctly, and how to fix it
@@ -47,6 +49,9 @@ Options for init:
47
49
  --tool <tool> An AI tool you use; repeat for several
48
50
  (${TOOL_IDS.join(", ")})
49
51
 
52
+ Options for migrate: the same as init. It never commits, and it won't start on
53
+ uncommitted changes. --dry-run prints what it would do, changing nothing.
54
+
50
55
  Options for assess:
51
56
  --target <stage> Assess against a stage other than the project's own,
52
57
  for example production before a launch
@@ -79,7 +84,8 @@ function oneOf(value, allowed, flag) {
79
84
  return value;
80
85
  throw new Error(`${flag} must be one of: ${allowed.join(", ")}`);
81
86
  }
82
- async function init(args, io) {
87
+ /** The options init and migrate share. */
88
+ function initOptions(args, io) {
83
89
  const { values } = parseArgs({
84
90
  args,
85
91
  strict: true,
@@ -95,7 +101,7 @@ async function init(args, io) {
95
101
  const stage = values.stage === undefined ? undefined : oneOf(values.stage, STAGES, "--stage");
96
102
  const team = values.team === undefined ? undefined : oneOf(values.team, ["solo", "team"], "--team");
97
103
  const tools = values.tool?.map((tool) => oneOf(tool, TOOL_IDS, "--tool"));
98
- return runInit({
104
+ return {
99
105
  cwd: io.cwd,
100
106
  yes: values.yes === true,
101
107
  dryRun: values["dry-run"] === true,
@@ -103,7 +109,13 @@ async function init(args, io) {
103
109
  ...(stage === undefined ? {} : { stage }),
104
110
  ...(team === undefined ? {} : { team }),
105
111
  ...(tools === undefined ? {} : { tools }),
106
- }, io.prompter, io.out);
112
+ };
113
+ }
114
+ function init(args, io) {
115
+ return runInit(initOptions(args, io), io.prompter, io.out);
116
+ }
117
+ function migrate(args, io) {
118
+ return runMigrate(initOptions(args, io), io.prompter, io.out);
107
119
  }
108
120
  function assess(args, io) {
109
121
  const { values } = parseArgs({
@@ -175,6 +187,7 @@ async function mcp(args, io) {
175
187
  }
176
188
  const COMMANDS = {
177
189
  init,
190
+ migrate,
178
191
  assess,
179
192
  render,
180
193
  doctor,
package/dist/doctor.js CHANGED
@@ -240,9 +240,11 @@ function checkGit(root) {
240
240
  function checkLegacy(root) {
241
241
  if (!LEGACY_MARKERS.some((marker) => existsSync(join(root, marker))))
242
242
  return [];
243
- return [
244
- warn("legacy", "The peer-ai/ folder is a copy of the v0 playbook, which Peer AI 1.0 doesn't read.", `Move any changes your project made to it into ${CONFIG_FILE}, then remove it with: git rm -r peer-ai`),
245
- ];
243
+ // migrate builds the config itself, so it only runs before there is one.
244
+ const fix = existsSync(join(root, CONFIG_FILE))
245
+ ? `Move any changes your project made to it into ${CONFIG_FILE}, then remove it with: git rm -r peer-ai`
246
+ : "Run npx peer-ai migrate. It moves what your project changed into the config, and removes the folder.";
247
+ return [warn("legacy", "The peer-ai/ folder is a copy of the v0 playbook, which Peer AI 1.0 doesn't read.", fix)];
246
248
  }
247
249
  export function diagnose(root, nodeVersion = process.versions.node, today = new Date(), options = {}) {
248
250
  const { check: configCheck, config } = checkConfig(root);
package/dist/init.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface Output {
17
17
  log: (line: string) => void;
18
18
  error: (line: string) => void;
19
19
  }
20
- interface Answers {
20
+ export interface Answers {
21
21
  name: string;
22
22
  description: string;
23
23
  tracks: DetectedTrack[];
@@ -26,6 +26,7 @@ interface Answers {
26
26
  tools: ToolId[];
27
27
  }
28
28
  export declare function describeTrack(track: DetectedTrack): string;
29
+ export declare function ask(detected: Detected, prompter: Prompter, title?: string): Promise<Answers>;
30
+ export declare function defaults(detected: Detected, options: InitOptions): Answers;
29
31
  export declare function buildConfig(detected: Detected, answers: Answers): Record<string, unknown>;
30
32
  export declare function runInit(options: InitOptions, prompter: Prompter | undefined, out: Output): Promise<number>;
31
- export {};
package/dist/init.js CHANGED
@@ -42,8 +42,8 @@ function parseStack(text) {
42
42
  .map((part) => part.trim().toLowerCase())
43
43
  .filter((part) => part !== "");
44
44
  }
45
- async function ask(detected, prompter) {
46
- prompter.intro("peer-ai init");
45
+ export async function ask(detected, prompter, title = "peer-ai init") {
46
+ prompter.intro(title);
47
47
  if (detected.tracks.length > 0) {
48
48
  prompter.note(detected.tracks.map(describeTrack).join("\n"), "Found in this repository");
49
49
  }
@@ -75,7 +75,7 @@ async function ask(detected, prompter) {
75
75
  }
76
76
  return { name, description, tracks, team, stage, tools };
77
77
  }
78
- function defaults(detected, options) {
78
+ export function defaults(detected, options) {
79
79
  return {
80
80
  name: options.name ?? detected.name,
81
81
  description: detected.description ?? "",
@@ -0,0 +1,95 @@
1
+ import { type ActivityId } from "peer-ai-workflow";
2
+ import { type Detected } from "./detect.ts";
3
+ import { type Answers, type InitOptions, type Output } from "./init.ts";
4
+ import { type Prompter } from "./prompter.ts";
5
+ import { type Converted, type Copy, type Fingerprints } from "./v0.ts";
6
+ export declare const NOTES_FILE = "docs/peer-ai-migration.md";
7
+ export declare const MIGRATION_ITEM = "migrate-v0";
8
+ export interface MigrateOptions extends InitOptions {
9
+ now?: Date;
10
+ /** The v0 fingerprints to compare against. Tests pass their own. */
11
+ fingerprints?: Fingerprints;
12
+ }
13
+ /** What git knows about the project, read once before anything changes. */
14
+ export interface RepoState {
15
+ /** The copy's files git tracks, which it can bring back after they're deleted. */
16
+ tracked: string[];
17
+ /** The copy's files git ignores, which migrate leaves where they are. */
18
+ ignored: string[];
19
+ /** The commit before the migration, so the notes can point at the files as they were. */
20
+ base?: string;
21
+ }
22
+ interface Decision {
23
+ title: string;
24
+ body: string[];
25
+ }
26
+ interface PlannedItem {
27
+ id?: string;
28
+ title: string;
29
+ stage: "prepare" | "build" | "verify";
30
+ track?: string;
31
+ position?: {
32
+ activity: ActivityId;
33
+ step: number;
34
+ };
35
+ next: string;
36
+ }
37
+ export interface Plan {
38
+ config: Record<string, unknown>;
39
+ copy: Copy;
40
+ converted: Converted[];
41
+ /** Files rewritten: instructions without v0's text, package.json without v0's scripts. */
42
+ edits: {
43
+ path: string;
44
+ text: string;
45
+ }[];
46
+ /** Files deleted outside the copy, each quoted in the notes or rebuilt from git. */
47
+ removals: string[];
48
+ /** The copy's tracked files, deleted one by one; its ignored files stay. */
49
+ copyFiles: string[];
50
+ decisions: Decision[];
51
+ /** Whole files kept in the notes as v0 had them, for reference. */
52
+ kept: {
53
+ path: string;
54
+ text: string;
55
+ info: string;
56
+ }[];
57
+ items: PlannedItem[];
58
+ base?: string;
59
+ }
60
+ /** A fenced block that holds any text, with a fence longer than any inside it. */
61
+ export declare function fenced(text: string, info?: string): string[];
62
+ /** Inline code that holds any text, even text with backticks of its own. */
63
+ export declare function code(text: string): string;
64
+ /** A work item id for a v0 ticket: PROJ-14 stays, #12 becomes PREFIX-12 or ITEM-12. */
65
+ export declare function ticketId(ticket: string, prefix: string | undefined): string | undefined;
66
+ export interface Scripts {
67
+ text: string;
68
+ removed: {
69
+ name: string;
70
+ command: string;
71
+ }[];
72
+ }
73
+ /**
74
+ * package.json without the scripts that only run v0's files. A script that does anything else as
75
+ * well stays as it is, and is listed for a person.
76
+ */
77
+ export declare function packageScripts(text: string): {
78
+ edit?: Scripts;
79
+ mentions: {
80
+ name: string;
81
+ command: string;
82
+ }[];
83
+ };
84
+ /** Works out everything migrate will do, without changing anything. */
85
+ export declare function planMigration(root: string, detected: Detected, answers: Answers, fingerprints: Fingerprints, repo: RepoState): Plan;
86
+ /** docs/peer-ai-migration.md: what migrate did, and every decision it left. */
87
+ export declare function notesDocument(plan: Plan, today: string): string;
88
+ /** A path that leads outside the project, through a symbolic link on the way or at the end. */
89
+ export declare function escapes(root: string, path: string): boolean;
90
+ /**
91
+ * Exit code 0 when migrated, or with --dry-run; 1 when it refuses, or when the migration was applied
92
+ * but a step after it didn't finish; 2 on a usage error.
93
+ */
94
+ export declare function runMigrate(options: MigrateOptions, prompter: Prompter | undefined, out: Output): Promise<number>;
95
+ export {};