peer-ai 1.0.0-next.1 → 1.0.0-next.2
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 +24 -19
- package/dist/doctor.js +4 -1
- package/dist/gate.d.ts +29 -0
- package/dist/gate.js +141 -0
- package/dist/migrate.js +9 -0
- package/dist/pipeline.d.ts +4 -0
- package/dist/pipeline.js +6 -5
- package/dist/render.js +8 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -81,10 +81,15 @@ It never overwrites an existing `peer-ai.config.json`, and it never assumes anyt
|
|
|
81
81
|
|
|
82
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
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
In the project's folder:
|
|
85
|
+
|
|
86
|
+
1. **Commit or stash any work in progress.** `migrate` won't start with uncommitted changes, so the move is a change of its own.
|
|
87
|
+
2. **Make a branch:** `git switch -c peer-ai-1.0`
|
|
88
|
+
3. **See the plan, changing nothing:** `npx peer-ai migrate --dry-run`
|
|
89
|
+
4. **Migrate:** `npx peer-ai migrate`. It asks the same few questions as `init`, already filled in from what it found.
|
|
90
|
+
5. **Review the change, commit it, and open a pull request.** To undo it instead: `git stash --include-untracked`.
|
|
91
|
+
6. **In your next session,** your AI tool brings up each decision `migrate` left in `docs/peer-ai-migration.md`. Go through them together, then delete the file.
|
|
92
|
+
7. **On GitHub,** make `peer-ai check` a required check, so nothing merges without the gate.
|
|
88
93
|
|
|
89
94
|
It converts what it can read with certainty:
|
|
90
95
|
|
|
@@ -101,7 +106,7 @@ Nothing is dropped silently. Whatever needs judgement, it copies word for word i
|
|
|
101
106
|
|
|
102
107
|
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
108
|
|
|
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
|
|
109
|
+
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 stash --include-untracked` undoes it all. It deletes only files git can bring back, leaves files git ignores where they are, and refuses to write outside the project.
|
|
105
110
|
|
|
106
111
|
### Options
|
|
107
112
|
|
|
@@ -200,6 +205,18 @@ npx peer-ai render
|
|
|
200
205
|
|
|
201
206
|
`AGENTS.md` gets the block whenever a tool other than Claude Code is listed, when it already exists, or when `CLAUDE.md` imports it.
|
|
202
207
|
|
|
208
|
+
### The CI gate
|
|
209
|
+
|
|
210
|
+
Render sets up `peer-ai check` in the project's CI, so no one has to remember to ([RFC 0009](../../rfcs/0009-the-ci-gate-set-up-by-render.md)):
|
|
211
|
+
|
|
212
|
+
- **On GitHub Actions,** it writes `.github/workflows/peer-ai.yml`: one job, `peer-ai check`, that installs Node 24 and runs the exact version of Peer AI render ran as, so it works with or without a `package.json`. Its actions are pinned to commits. Render keeps the file up to date while it's as render left it, and leaves it alone once someone changes it by hand; `doctor` then checks it still runs the gate. A version bump updates it.
|
|
213
|
+
- **On any other CI,** it prints the step to add.
|
|
214
|
+
- **When a workflow of the project's own already runs `peer-ai check`,** it adds nothing.
|
|
215
|
+
|
|
216
|
+
Make `peer-ai check` a required check in the repository's settings, so nothing merges without it. To run the gate some other way, set `"delivery": { "gate": false }`.
|
|
217
|
+
|
|
218
|
+
### Instructions
|
|
219
|
+
|
|
203
220
|
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.
|
|
204
221
|
|
|
205
222
|
### Skills
|
|
@@ -280,6 +297,7 @@ npx peer-ai doctor
|
|
|
280
297
|
| AI tools | A tool set up in the repository, such as a `CLAUDE.md` or `.cursor/`, that the config doesn't list |
|
|
281
298
|
| What render writes | Instructions or MCP registrations that no longer match the config, or skills that are missing or out of date |
|
|
282
299
|
| CI | A config that says there is no CI when the repository has a pipeline, which would lead Peer AI to add a second one |
|
|
300
|
+
| The CI gate | A pipeline that doesn't run `peer-ai check`, or a gate workflow that's out of date or no longer runs it. Through `next_work`, the AI tool hears about it in every session. |
|
|
283
301
|
| The project map | Missing, not valid, or out of date. It runs a fresh assessment and lists every item whose status has changed since `.peer-ai/map.json` was written. |
|
|
284
302
|
| Work items | A file in `.peer-ai/work/` that isn't valid, isn't named after its id, or names a track the config doesn't have |
|
|
285
303
|
| Git | A folder that isn't a git repository, or a `.gitignore` that hides Peer AI's files from the team and CI |
|
|
@@ -337,20 +355,7 @@ A review's result is worked out from its report when it is recorded, so an open
|
|
|
337
355
|
|
|
338
356
|
### In CI
|
|
339
357
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
```yaml
|
|
343
|
-
- run: npx peer-ai check
|
|
344
|
-
```
|
|
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
|
-
```
|
|
358
|
+
`render` sets it up: see [the CI gate](#the-ci-gate). By hand, it's a step after the project's own checks that runs, with Node 24, `npx -y peer-ai@<version> check`.
|
|
354
359
|
|
|
355
360
|
### Options
|
|
356
361
|
|
package/dist/doctor.js
CHANGED
|
@@ -10,6 +10,7 @@ import { DOMAINS } from "peer-ai-workflow";
|
|
|
10
10
|
import { LEGACY_MARKERS, MAP_FILE, assess, loadConfig } from "./assess.js";
|
|
11
11
|
import { count, fail, formatChecks, ok, plural, skip, warn } from "./checks.js";
|
|
12
12
|
import { checkEnforcers, checkProfiles } from "./enforcers.js";
|
|
13
|
+
import { GATE_FILE, checkGate } from "./gate.js";
|
|
13
14
|
import { WORKFLOW_FILE } from "./pipeline.js";
|
|
14
15
|
import { RUFF_FILE } from "./ruff.js";
|
|
15
16
|
import { CONFIG_FILE, detectDelivery, detectName, detectTools, detectTracks } from "./detect.js";
|
|
@@ -105,7 +106,8 @@ function checkTools(root, config) {
|
|
|
105
106
|
return ok("tools", `AI tools: ${listed.join(", ")}`);
|
|
106
107
|
}
|
|
107
108
|
/** What render writes for the AI tools still matches the config. */
|
|
108
|
-
|
|
109
|
+
/** Files with a check of their own: the enforcers, and the CI gate. */
|
|
110
|
+
const ENFORCER_FILES = [WORKFLOW_FILE, RUFF_FILE, GATE_FILE];
|
|
109
111
|
function checkRendered(root, config, skills) {
|
|
110
112
|
const plan = planRender(root, config);
|
|
111
113
|
const withSkills = skills || config.skills?.commit === true;
|
|
@@ -259,6 +261,7 @@ export function diagnose(root, nodeVersion = process.versions.node, today = new
|
|
|
259
261
|
? needsConfig("render", "What render writes")
|
|
260
262
|
: [checkRendered(root, config, options.skills ?? true)]),
|
|
261
263
|
...(config === undefined ? needsConfig("delivery", "CI") : [checkDelivery(root, config)]),
|
|
264
|
+
...(config === undefined ? needsConfig("gate", "The CI gate") : checkGate(root, config)),
|
|
262
265
|
...(config === undefined
|
|
263
266
|
? needsConfig("standards", "Rules set aside")
|
|
264
267
|
: checkStandards(config, today.toISOString().slice(0, 10))),
|
package/dist/gate.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PeerAiConfig } from "peer-ai-workflow";
|
|
2
|
+
import { type Check } from "./checks.ts";
|
|
3
|
+
export declare const GATE_FILE = ".github/workflows/peer-ai.yml";
|
|
4
|
+
/** The gate's command, with the exact version render runs as, so CI needs no package.json. */
|
|
5
|
+
export declare const gateCommand: () => string;
|
|
6
|
+
/** The workflow render writes for the gate: Node 24 and the exact version, so any project can run it. */
|
|
7
|
+
export declare function gateWorkflow(config: PeerAiConfig): string;
|
|
8
|
+
export interface Gate {
|
|
9
|
+
/** What render does to the gate's workflow, on GitHub Actions. */
|
|
10
|
+
planned?: {
|
|
11
|
+
path: string;
|
|
12
|
+
action: "create" | "update" | "unchanged" | "kept";
|
|
13
|
+
content?: string;
|
|
14
|
+
note?: string;
|
|
15
|
+
};
|
|
16
|
+
/** The step to add by hand, for any other CI. */
|
|
17
|
+
manual?: string;
|
|
18
|
+
/** Where the gate runs already, when it does. */
|
|
19
|
+
runsIn?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Whether the project's CI is GitHub Actions: workflows of its own, or the gate render wrote. */
|
|
22
|
+
export declare function onGitHubActions(root: string): boolean;
|
|
23
|
+
/** Where the gate stands, and what render does about it. */
|
|
24
|
+
export declare function planGate(root: string, config: PeerAiConfig): Gate;
|
|
25
|
+
/**
|
|
26
|
+
* peer-ai doctor's check: CI runs the gate, unless the config turns it off. With no CI yet there is
|
|
27
|
+
* nothing to say; the delivery check reports that already.
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkGate(root: string, config: PeerAiConfig): Check[];
|
package/dist/gate.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// The CI gate (RFC 0009): CI runs peer-ai check on every change, so work is held to its record
|
|
2
|
+
// without anyone having to remember to set that up. On GitHub Actions, render writes a workflow of
|
|
3
|
+
// its own for it, owned the way the security workflow is: rewritten while it's as render left it,
|
|
4
|
+
// and left alone once someone changes it by hand. For any other CI, render prints the step to add.
|
|
5
|
+
// peer-ai doctor warns while nothing runs it, so the AI tool hears about it in every session.
|
|
6
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { ok, skip, warn } from "./checks.js";
|
|
9
|
+
import { detectDelivery } from "./detect.js";
|
|
10
|
+
import { VERSION } from "./package-info.js";
|
|
11
|
+
import { CHECKOUT, SETUP_NODE, WORKFLOW_FILE, sameFile, unchangedSinceRender, withHash } from "./pipeline.js";
|
|
12
|
+
export const GATE_FILE = ".github/workflows/peer-ai.yml";
|
|
13
|
+
const WORKFLOWS = ".github/workflows";
|
|
14
|
+
/** Workflows Peer AI writes for itself, which on their own aren't a project's CI. */
|
|
15
|
+
const OWN = [GATE_FILE, WORKFLOW_FILE, `${WORKFLOWS}/copilot-setup-steps.yml`];
|
|
16
|
+
/** A command that runs the gate: peer-ai check, with or without a version. */
|
|
17
|
+
const GATE_COMMAND = /\bpeer-ai(@\S+)?\s+check\b/;
|
|
18
|
+
/** Whether a CI file runs the gate: a line with the command, not a comment or a job's name. */
|
|
19
|
+
const runsGate = (text) => text.split(/\r?\n/).some((line) => !/^\s*#/.test(line) && !/^\s*-?\s*name:/.test(line) && GATE_COMMAND.test(line));
|
|
20
|
+
/** The gate's command, with the exact version render runs as, so CI needs no package.json. */
|
|
21
|
+
export const gateCommand = () => `npx -y peer-ai@${VERSION} check`;
|
|
22
|
+
const read = (root, path) => {
|
|
23
|
+
const full = join(root, path);
|
|
24
|
+
return existsSync(full) && statSync(full).isFile() ? readFileSync(full, "utf8") : undefined;
|
|
25
|
+
};
|
|
26
|
+
/** The files of a pipeline: a folder's YAML files, or the file itself. */
|
|
27
|
+
function filesOf(root, pipeline) {
|
|
28
|
+
if (!pipeline.endsWith("/"))
|
|
29
|
+
return [pipeline];
|
|
30
|
+
const dir = join(root, pipeline);
|
|
31
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory())
|
|
32
|
+
return [];
|
|
33
|
+
return readdirSync(dir)
|
|
34
|
+
.filter((name) => /\.ya?ml$/.test(name))
|
|
35
|
+
.sort()
|
|
36
|
+
.map((name) => `${pipeline}${name}`);
|
|
37
|
+
}
|
|
38
|
+
/** The workflow render writes for the gate: Node 24 and the exact version, so any project can run it. */
|
|
39
|
+
export function gateWorkflow(config) {
|
|
40
|
+
const branch = config.repo?.defaultBranch;
|
|
41
|
+
const body = [
|
|
42
|
+
"name: Peer AI",
|
|
43
|
+
"on:",
|
|
44
|
+
" pull_request:",
|
|
45
|
+
// With no default branch in the config, changes are checked in their pull requests.
|
|
46
|
+
...(branch === undefined ? [] : [" push:", ` branches: [${branch}]`]),
|
|
47
|
+
"permissions:",
|
|
48
|
+
" contents: read",
|
|
49
|
+
"jobs:",
|
|
50
|
+
" peer-ai-check:",
|
|
51
|
+
" name: peer-ai check",
|
|
52
|
+
" runs-on: ubuntu-latest",
|
|
53
|
+
" timeout-minutes: 10",
|
|
54
|
+
" steps:",
|
|
55
|
+
` - uses: ${CHECKOUT}`,
|
|
56
|
+
" with:",
|
|
57
|
+
" persist-credentials: false",
|
|
58
|
+
` - uses: ${SETUP_NODE}`,
|
|
59
|
+
" with:",
|
|
60
|
+
" node-version: 24",
|
|
61
|
+
` - run: ${gateCommand()}`,
|
|
62
|
+
"",
|
|
63
|
+
].join("\n");
|
|
64
|
+
return withHash([
|
|
65
|
+
"# Generated by peer-ai render from peer-ai.config.json: the gate, peer-ai check (RFC 0009).",
|
|
66
|
+
"# Change the config, not this file. Render writes it again while it's as render left it, and leaves",
|
|
67
|
+
"# it alone once someone changes it by hand. Make the job a required check: its name never changes.",
|
|
68
|
+
], body);
|
|
69
|
+
}
|
|
70
|
+
/** Whether the project's CI is GitHub Actions: workflows of its own, or the gate render wrote. */
|
|
71
|
+
export function onGitHubActions(root) {
|
|
72
|
+
return read(root, GATE_FILE) !== undefined || filesOf(root, `${WORKFLOWS}/`).some((path) => !OWN.includes(path));
|
|
73
|
+
}
|
|
74
|
+
/** Where the gate stands, and what render does about it. */
|
|
75
|
+
export function planGate(root, config) {
|
|
76
|
+
if (config.delivery?.gate === false)
|
|
77
|
+
return {};
|
|
78
|
+
const existing = read(root, GATE_FILE);
|
|
79
|
+
const others = filesOf(root, `${WORKFLOWS}/`).filter((path) => path !== GATE_FILE);
|
|
80
|
+
const onActions = onGitHubActions(root) || (config.delivery?.pipeline ?? "").startsWith(WORKFLOWS);
|
|
81
|
+
if (onActions) {
|
|
82
|
+
if (existing === undefined) {
|
|
83
|
+
// A gate someone added to a workflow of their own is enough: there is never a second one.
|
|
84
|
+
const elsewhere = others.find((path) => runsGate(read(root, path) ?? ""));
|
|
85
|
+
if (elsewhere !== undefined)
|
|
86
|
+
return { runsIn: elsewhere };
|
|
87
|
+
return { planned: { path: GATE_FILE, action: "create", content: gateWorkflow(config) } };
|
|
88
|
+
}
|
|
89
|
+
if (unchangedSinceRender(existing)) {
|
|
90
|
+
const content = gateWorkflow(config);
|
|
91
|
+
const same = sameFile(existing, content);
|
|
92
|
+
return {
|
|
93
|
+
planned: { path: GATE_FILE, action: same ? "unchanged" : "update", ...(same ? {} : { content }) },
|
|
94
|
+
runsIn: GATE_FILE,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
planned: {
|
|
99
|
+
path: GATE_FILE,
|
|
100
|
+
action: "kept",
|
|
101
|
+
note: "It was changed by hand, so keeping it up to date is yours now; peer-ai doctor checks it still runs peer-ai check. Delete it to have render write Peer AI's again.",
|
|
102
|
+
},
|
|
103
|
+
...(runsGate(existing) ? { runsIn: GATE_FILE } : {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const pipeline = detectDelivery(root)?.pipeline ?? (config.delivery?.ci === "existing" ? config.delivery.pipeline : undefined);
|
|
107
|
+
// A workflows folder that holds only Peer AI's own files isn't the project's CI.
|
|
108
|
+
if (pipeline === undefined || pipeline.startsWith(WORKFLOWS))
|
|
109
|
+
return {};
|
|
110
|
+
const runs = filesOf(root, pipeline).find((path) => runsGate(read(root, path) ?? ""));
|
|
111
|
+
if (runs !== undefined)
|
|
112
|
+
return { runsIn: runs };
|
|
113
|
+
return {
|
|
114
|
+
manual: `Your CI, ${pipeline}, doesn't run Peer AI's gate. Add a step that runs, with Node 24, after the project's own checks: ${gateCommand()}`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const NOT_GATED = "No CI runs peer-ai check, so nothing holds work to its record in CI.";
|
|
118
|
+
/**
|
|
119
|
+
* peer-ai doctor's check: CI runs the gate, unless the config turns it off. With no CI yet there is
|
|
120
|
+
* nothing to say; the delivery check reports that already.
|
|
121
|
+
*/
|
|
122
|
+
export function checkGate(root, config) {
|
|
123
|
+
if (config.delivery?.gate === false)
|
|
124
|
+
return [skip("gate", "The CI gate is off: delivery.gate is false.")];
|
|
125
|
+
const gate = planGate(root, config);
|
|
126
|
+
const action = gate.planned?.action;
|
|
127
|
+
if (action === "create")
|
|
128
|
+
return [warn("gate", NOT_GATED, `Run npx peer-ai render. It writes ${GATE_FILE}.`)];
|
|
129
|
+
if (action === "update")
|
|
130
|
+
return [warn("gate", `${GATE_FILE} is out of date.`, "Run npx peer-ai render.")];
|
|
131
|
+
if (action === "kept" && gate.runsIn === undefined) {
|
|
132
|
+
return [
|
|
133
|
+
warn("gate", `${GATE_FILE} was changed by hand, and no longer runs peer-ai check.`, `Put the step back: - run: ${gateCommand()}`),
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
if (gate.runsIn !== undefined)
|
|
137
|
+
return [ok("gate", `CI runs peer-ai check: ${gate.runsIn}`)];
|
|
138
|
+
if (gate.manual !== undefined)
|
|
139
|
+
return [warn("gate", NOT_GATED, gate.manual)];
|
|
140
|
+
return [];
|
|
141
|
+
}
|
package/dist/migrate.js
CHANGED
|
@@ -11,6 +11,7 @@ import { dirname, join, sep } from "node:path";
|
|
|
11
11
|
import { validateConfig } from "peer-ai-workflow";
|
|
12
12
|
import { LEGACY_MARKERS, loadConfig, runAssess } from "./assess.js";
|
|
13
13
|
import { CONFIG_FILE, detect } from "./detect.js";
|
|
14
|
+
import { GATE_FILE, onGitHubActions } from "./gate.js";
|
|
14
15
|
import { ask, buildConfig, defaults } from "./init.js";
|
|
15
16
|
import { Cancelled } from "./prompter.js";
|
|
16
17
|
import { runRender } from "./render.js";
|
|
@@ -467,6 +468,14 @@ export function planMigration(root, detected, answers, fingerprints, repo) {
|
|
|
467
468
|
if (leftAlone.length > 0) {
|
|
468
469
|
decisions.push({ title: "Left as they were", body: leftAlone.map((line) => `- ${line}`) });
|
|
469
470
|
}
|
|
471
|
+
if (onGitHubActions(root)) {
|
|
472
|
+
decisions.push({
|
|
473
|
+
title: "Make Peer AI's CI gate a required check",
|
|
474
|
+
body: [
|
|
475
|
+
`\`render\` added ${code(GATE_FILE)}, which runs \`peer-ai check\` on every pull request (RFC 0009). Only the repository's owner can make a check required: in GitHub, add the status check \`peer-ai check\` to the main branch's rules, so no change merges without it.`,
|
|
476
|
+
],
|
|
477
|
+
});
|
|
478
|
+
}
|
|
470
479
|
// The config: init's, plus everything converted.
|
|
471
480
|
const config = buildConfig(detected, answers);
|
|
472
481
|
const tracks = config.tracks.map((track) => dormantTracks.has(String(track.id)) ? { ...track, status: "dormant" } : track);
|
package/dist/pipeline.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type PipelineJob } from "peer-ai-standards";
|
|
2
2
|
import type { PeerAiConfig } from "peer-ai-workflow";
|
|
3
3
|
export declare const WORKFLOW_FILE = ".github/workflows/peer-ai-security.yml";
|
|
4
|
+
export declare const CHECKOUT = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1";
|
|
5
|
+
export declare const SETUP_NODE = "actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0";
|
|
4
6
|
/** A container image, pinned to its digest: Docker uses the digest, and the tag says which release it is. */
|
|
5
7
|
export interface Image {
|
|
6
8
|
name: string;
|
|
@@ -71,6 +73,8 @@ export declare function pipelineRules(config: PeerAiConfig): import("peer-ai-sta
|
|
|
71
73
|
export declare function workflowBody(config: PeerAiConfig): string | undefined;
|
|
72
74
|
/** The jobs the workflow render writes would run: the keys under jobs:. */
|
|
73
75
|
export declare function workflowJobs(config: PeerAiConfig): string[];
|
|
76
|
+
/** A file render owns: its header, a line recording the body's hash, then the body. */
|
|
77
|
+
export declare const withHash: (header: string[], body: string) => string;
|
|
74
78
|
/** The whole file render writes: a header recording the body's hash, then the body. */
|
|
75
79
|
export declare function workflowFile(config: PeerAiConfig): string | undefined;
|
|
76
80
|
/** Whether a file was written by render: it records a hash in its header. */
|
package/dist/pipeline.js
CHANGED
|
@@ -11,7 +11,8 @@ import { PROFILES, profileRulesFor } from "peer-ai-standards";
|
|
|
11
11
|
export const WORKFLOW_FILE = ".github/workflows/peer-ai-security.yml";
|
|
12
12
|
/** The folder the tools are installed in, on the PATH of the job's later steps. */
|
|
13
13
|
const TOOLS = '"$RUNNER_TEMP/peer-ai-tools"';
|
|
14
|
-
const CHECKOUT = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1";
|
|
14
|
+
export const CHECKOUT = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1";
|
|
15
|
+
export const SETUP_NODE = "actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0";
|
|
15
16
|
const UPLOAD = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1";
|
|
16
17
|
const GITLEAKS = {
|
|
17
18
|
version: "8.30.1",
|
|
@@ -237,18 +238,18 @@ export function workflowJobs(config) {
|
|
|
237
238
|
const normalise = (text) => text.replaceAll("\r\n", "\n");
|
|
238
239
|
const hash = (body) => createHash("sha256").update(normalise(body)).digest("hex").slice(0, 16);
|
|
239
240
|
const HASH_LINE = /^# peer-ai sha256: ([0-9a-f]{16})$/m;
|
|
241
|
+
/** A file render owns: its header, a line recording the body's hash, then the body. */
|
|
242
|
+
export const withHash = (header, body) => [...header, `# peer-ai sha256: ${hash(body)}`, body].join("\n");
|
|
240
243
|
/** The whole file render writes: a header recording the body's hash, then the body. */
|
|
241
244
|
export function workflowFile(config) {
|
|
242
245
|
const body = workflowBody(config);
|
|
243
246
|
if (body === undefined)
|
|
244
247
|
return undefined;
|
|
245
|
-
return [
|
|
248
|
+
return withHash([
|
|
246
249
|
"# Generated by peer-ai render from peer-ai.config.json: the checks of the github-actions stack profile.",
|
|
247
250
|
"# Change the config, not this file. Render writes it again while it's as render left it, and leaves",
|
|
248
251
|
"# it alone once someone changes it by hand. Make each job a required check: their names never change.",
|
|
249
|
-
|
|
250
|
-
body,
|
|
251
|
-
].join("\n");
|
|
252
|
+
], body);
|
|
252
253
|
}
|
|
253
254
|
/** Whether a file was written by render: it records a hash in its header. */
|
|
254
255
|
export const writtenByRender = (content) => HASH_LINE.test(normalise(content));
|
package/dist/render.js
CHANGED
|
@@ -16,7 +16,8 @@ import { count, fail, formatChecks, ok, plural, skip } from "./checks.js";
|
|
|
16
16
|
import { CONFIG_FILE } from "./detect.js";
|
|
17
17
|
import { FEEDBACK_DIR } from "./feedback.js";
|
|
18
18
|
import { VERSION } from "./package-info.js";
|
|
19
|
-
import {
|
|
19
|
+
import { planGate } from "./gate.js";
|
|
20
|
+
import { CHECKOUT, SETUP_NODE, WORKFLOW_FILE, sameFile, unchangedSinceRender, workflowFile } from "./pipeline.js";
|
|
20
21
|
import { RUFF_FILE, ruffFile } from "./ruff.js";
|
|
21
22
|
export const START = "<!-- peer-ai:start -->";
|
|
22
23
|
export const END = "<!-- peer-ai:end -->";
|
|
@@ -325,8 +326,6 @@ function cursorEnvironment(root, command) {
|
|
|
325
326
|
return { ...environment, start: next };
|
|
326
327
|
}, `add to its "start" command: ${command}`);
|
|
327
328
|
}
|
|
328
|
-
const CHECKOUT = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1";
|
|
329
|
-
const SETUP_NODE = "actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0";
|
|
330
329
|
/**
|
|
331
330
|
* Copilot's cloud agent runs the copilot-setup-steps job before it starts. A workflow that already
|
|
332
331
|
* exists is left to its owner, with the step to add.
|
|
@@ -472,6 +471,12 @@ export function planRender(root, config) {
|
|
|
472
471
|
});
|
|
473
472
|
}
|
|
474
473
|
}
|
|
474
|
+
// The CI gate, peer-ai check (RFC 0009): a workflow of its own on GitHub Actions, a step to add elsewhere.
|
|
475
|
+
const gate = planGate(root, config);
|
|
476
|
+
if (gate.planned !== undefined)
|
|
477
|
+
files.push(gate.planned);
|
|
478
|
+
if (gate.manual !== undefined)
|
|
479
|
+
manual.push(gate.manual);
|
|
475
480
|
if (uses("claude-code"))
|
|
476
481
|
files.push(claudeSessionHook(root, command));
|
|
477
482
|
if (uses("cursor"))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peer-ai",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.2",
|
|
4
4
|
"description": "Keeps AI coding tools to a senior team's standard: planning, testing, security and compliance, with proof of every check",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@clack/prompts": "1.8.1",
|
|
12
12
|
"@modelcontextprotocol/sdk": "1.30.1",
|
|
13
|
-
"peer-ai-skills": "1.0.0-next.
|
|
14
|
-
"peer-ai-standards": "1.0.0-next.
|
|
15
|
-
"peer-ai-workflow": "1.0.0-next.
|
|
13
|
+
"peer-ai-skills": "1.0.0-next.2",
|
|
14
|
+
"peer-ai-standards": "1.0.0-next.2",
|
|
15
|
+
"peer-ai-workflow": "1.0.0-next.2",
|
|
16
16
|
"jsonc-parser": "3.3.1",
|
|
17
17
|
"smol-toml": "1.9.0",
|
|
18
18
|
"yaml": "2.9.1",
|