@alexandrealvaro/agentic 0.7.0-beta.1 → 0.9.0-beta.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 +29 -0
- package/package.json +2 -2
- package/src/commands/init.js +129 -46
- package/src/commands/profile.js +165 -0
- package/src/commands/update.js +81 -20
- package/src/index.js +9 -0
- package/src/lib/profiles.js +128 -0
- package/src/lib/state.js +17 -1
- package/src/skills/claude-code/agentic-adr/SKILL.md +4 -0
- package/src/skills/claude-code/agentic-architecture/SKILL.md +2 -0
- package/src/skills/claude-code/agentic-bootstrap/SKILL.md +2 -0
- package/src/skills/claude-code/agentic-ground/SKILL.md +2 -0
- package/src/skills/claude-code/agentic-review/SKILL.md +2 -0
- package/src/skills/claude-code/agentic-spec/SKILL.md +4 -0
- package/src/skills/claude-code/agentic-task/SKILL.md +4 -0
package/README.md
CHANGED
|
@@ -46,6 +46,35 @@ A short TUI shows the detected mode, agent, and feature signals (frontend / `.cl
|
|
|
46
46
|
|
|
47
47
|
If your project already has an `AGENTS.md` (or `CLAUDE.md`), the installer appends a managed `Skills installed by agentic` section bracketed by `<!-- agentic-managed-skills:start -->` / `:end -->` markers. User content outside those markers is byte-preserved; re-runs update only the managed block.
|
|
48
48
|
|
|
49
|
+
## Project maturity profiles
|
|
50
|
+
|
|
51
|
+
The kit ships four profiles that select which skills auto-install. Same WORKFLOW principles bind every profile; only the artifact set scales.
|
|
52
|
+
|
|
53
|
+
| Profile | Universal install set | Conditional posture | Recommended for |
|
|
54
|
+
| --- | --- | --- | --- |
|
|
55
|
+
| `poc` | philosophy, ground, audit | all blocked | spike, hackathon, exploration |
|
|
56
|
+
| `solo` | + bootstrap, spec, task, review | architecture / adr / hooks opt-in; design auto if frontend; subagent auto for Claude Code | solo developer shipping a real product |
|
|
57
|
+
| `team` (default) | + architecture, adr | hooks opt-in; design / subagent / skill follow autoIf | team product, shared discipline |
|
|
58
|
+
| `mature` | same as team | hooks **recommended**; future evals + spike skills land here | regulated / public-facing production |
|
|
59
|
+
|
|
60
|
+
Select at init time:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx @alexandrealvaro/agentic@beta init --profile poc
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or change later:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx @alexandrealvaro/agentic@beta profile set solo # add solo's universal skills
|
|
70
|
+
npx @alexandrealvaro/agentic@beta profile show # show current per-agent profile
|
|
71
|
+
npx @alexandrealvaro/agentic@beta profile list # list all profiles
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`profile set` runs the equivalent of `update` after writing the new profile, so the install set matches. The state-aware three-way diff prompts before overwriting user-edited files.
|
|
75
|
+
|
|
76
|
+
Existing v0.7 installs with no profile field migrate to `team` automatically — same install set as before, no user action needed.
|
|
77
|
+
|
|
49
78
|
## Updating an existing project
|
|
50
79
|
|
|
51
80
|
To pull upstream kit changes into a project that already has agentic skills installed:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexandrealvaro/agentic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-beta.1",
|
|
4
4
|
"description": "Bootstrap and audit AGENTS.md, ARCHITECTURE.md, ADRs, skills, and subagents for engineering production code with LLMs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"start": "node bin/agentic.js",
|
|
23
|
-
"test": "node bin/agentic.js --help && node bin/agentic.js init --help && node bin/agentic.js update --help && node --test test/*.test.js",
|
|
23
|
+
"test": "node bin/agentic.js --help && node bin/agentic.js init --help && node bin/agentic.js update --help && node bin/agentic.js profile --help && node --test test/*.test.js",
|
|
24
24
|
"prepublishOnly": "npm test"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
package/src/commands/init.js
CHANGED
|
@@ -5,6 +5,14 @@ import { dirname, join } from 'node:path';
|
|
|
5
5
|
import { detectAgents, detectFeatures, detectMode } from '../lib/detect.js';
|
|
6
6
|
import { installSkills } from '../lib/install.js';
|
|
7
7
|
import { saveState, loadState } from '../lib/state.js';
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_PROFILE,
|
|
10
|
+
PROFILES,
|
|
11
|
+
PROFILE_NAMES,
|
|
12
|
+
availableConditionalsForProfile,
|
|
13
|
+
profileOrDefault,
|
|
14
|
+
requiredSkillsForProfile,
|
|
15
|
+
} from '../lib/profiles.js';
|
|
8
16
|
import { updateRootDoc } from '../lib/rootdoc.js';
|
|
9
17
|
|
|
10
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -43,23 +51,18 @@ const ROOT_DOC_LABEL = {
|
|
|
43
51
|
absent: '',
|
|
44
52
|
};
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'agentic-task',
|
|
53
|
-
'agentic-audit',
|
|
54
|
-
'agentic-review',
|
|
55
|
-
'agentic-ground',
|
|
56
|
-
];
|
|
54
|
+
/**
|
|
55
|
+
* Backward-compatible export: the `team` profile's universal skill list.
|
|
56
|
+
* Tests and downstream code that imported REQUIRED_SKILLS pre-v0.8 get the
|
|
57
|
+
* same list. New code should call `requiredSkillsForProfile(profileName)`.
|
|
58
|
+
*/
|
|
59
|
+
export const REQUIRED_SKILLS = requiredSkillsForProfile('team');
|
|
57
60
|
|
|
58
61
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
62
|
+
* Backward-compatible export: the v0.7-shape conditional skill catalog.
|
|
63
|
+
* The four entries match the four conditional skills available in v0.7
|
|
64
|
+
* with their autoIf / agents / hint configuration. The profile-aware
|
|
65
|
+
* install path overrides `autoIf` per profile via `availableConditionalsForProfile`.
|
|
63
66
|
*/
|
|
64
67
|
export const CONDITIONAL_SKILLS = [
|
|
65
68
|
{
|
|
@@ -92,6 +95,10 @@ export const CONDITIONAL_SKILLS = [
|
|
|
92
95
|
},
|
|
93
96
|
];
|
|
94
97
|
|
|
98
|
+
const CONDITIONAL_BY_NAME = Object.fromEntries(
|
|
99
|
+
CONDITIONAL_SKILLS.map((s) => [s.name, s])
|
|
100
|
+
);
|
|
101
|
+
|
|
95
102
|
function resolveAgents(flagValue, detectedAgents) {
|
|
96
103
|
if (flagValue === 'both') return ['claude-code', 'codex'];
|
|
97
104
|
if (flagValue) return [flagValue];
|
|
@@ -99,18 +106,39 @@ function resolveAgents(flagValue, detectedAgents) {
|
|
|
99
106
|
return ['claude-code'];
|
|
100
107
|
}
|
|
101
108
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Translate a profile rule (`'frontend'`, `'claude-code'`, `true`, `false`)
|
|
111
|
+
* into a boolean: should this conditional auto-install for the current
|
|
112
|
+
* features and target agents?
|
|
113
|
+
*/
|
|
114
|
+
function evaluateRule(rule, features, targetAgents) {
|
|
115
|
+
if (rule === 'frontend') return features.frontend === true;
|
|
116
|
+
if (rule === 'claude-code') return targetAgents.includes('claude-code');
|
|
117
|
+
if (rule === true) return true;
|
|
118
|
+
if (rule === false) return false;
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function pickConditionalAuto(features, targetAgents, profileName) {
|
|
123
|
+
const out = [];
|
|
124
|
+
for (const { name, rule } of availableConditionalsForProfile(profileName)) {
|
|
125
|
+
const def = CONDITIONAL_BY_NAME[name];
|
|
126
|
+
if (!def) continue;
|
|
127
|
+
if (!def.agents.some((a) => targetAgents.includes(a))) continue;
|
|
128
|
+
if (evaluateRule(rule, features, targetAgents)) {
|
|
129
|
+
out.push(name);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
106
133
|
}
|
|
107
134
|
|
|
108
|
-
function skillsForAgent(agent, optedSkills) {
|
|
135
|
+
function skillsForAgent(agent, profileName, optedSkills) {
|
|
136
|
+
const universal = requiredSkillsForProfile(profileName);
|
|
109
137
|
const conditional = optedSkills.filter((skillName) => {
|
|
110
|
-
const def =
|
|
138
|
+
const def = CONDITIONAL_BY_NAME[skillName];
|
|
111
139
|
return def && def.agents.includes(agent);
|
|
112
140
|
});
|
|
113
|
-
return [...
|
|
141
|
+
return [...universal, ...conditional];
|
|
114
142
|
}
|
|
115
143
|
|
|
116
144
|
export async function initCommand(opts) {
|
|
@@ -120,6 +148,12 @@ export async function initCommand(opts) {
|
|
|
120
148
|
);
|
|
121
149
|
}
|
|
122
150
|
|
|
151
|
+
if (opts.profile && !PROFILE_NAMES.includes(opts.profile)) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`invalid profile "${opts.profile}". Use one of: ${PROFILE_NAMES.join(', ')}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
123
157
|
const cwd = process.cwd();
|
|
124
158
|
const interactive = process.stdout.isTTY && !opts.yes && !opts.agent;
|
|
125
159
|
|
|
@@ -127,18 +161,20 @@ export async function initCommand(opts) {
|
|
|
127
161
|
const detectedAgents = detectAgents(cwd);
|
|
128
162
|
const features = detectFeatures(cwd);
|
|
129
163
|
|
|
164
|
+
let profileName = profileOrDefault(opts.profile);
|
|
130
165
|
let agents;
|
|
131
166
|
let optedSkills;
|
|
132
167
|
|
|
133
168
|
if (interactive) {
|
|
134
169
|
p.intro('agentic init');
|
|
135
|
-
const featureLine =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
170
|
+
const featureLine =
|
|
171
|
+
[
|
|
172
|
+
features.frontend ? 'frontend' : null,
|
|
173
|
+
features.hasClaudeCode ? '.claude/ present' : null,
|
|
174
|
+
features.hasCodex ? '.agents/.openai/ present' : null,
|
|
175
|
+
]
|
|
176
|
+
.filter(Boolean)
|
|
177
|
+
.join(', ') || 'none';
|
|
142
178
|
|
|
143
179
|
p.note(
|
|
144
180
|
`Mode: ${MODE_LABEL[detectedMode]}\n` +
|
|
@@ -151,6 +187,21 @@ export async function initCommand(opts) {
|
|
|
151
187
|
'Detected context'
|
|
152
188
|
);
|
|
153
189
|
|
|
190
|
+
const profileChoice = await p.select({
|
|
191
|
+
message: 'Project maturity profile?',
|
|
192
|
+
options: PROFILE_NAMES.map((name) => ({
|
|
193
|
+
value: name,
|
|
194
|
+
label: name,
|
|
195
|
+
hint: PROFILES[name].note,
|
|
196
|
+
})),
|
|
197
|
+
initialValue: profileName,
|
|
198
|
+
});
|
|
199
|
+
if (p.isCancel(profileChoice)) {
|
|
200
|
+
p.cancel('Cancelled.');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
profileName = profileChoice;
|
|
204
|
+
|
|
154
205
|
const choice = await p.select({
|
|
155
206
|
message: 'Install skills for which agent(s)?',
|
|
156
207
|
options: [
|
|
@@ -169,14 +220,20 @@ export async function initCommand(opts) {
|
|
|
169
220
|
}
|
|
170
221
|
agents = choice;
|
|
171
222
|
|
|
172
|
-
const conditionalOptions =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
223
|
+
const conditionalOptions = availableConditionalsForProfile(profileName)
|
|
224
|
+
.map(({ name, rule }) => {
|
|
225
|
+
const def = CONDITIONAL_BY_NAME[name];
|
|
226
|
+
if (!def) return null;
|
|
227
|
+
if (!def.agents.some((a) => agents.includes(a))) return null;
|
|
228
|
+
const auto = evaluateRule(rule, features, agents);
|
|
229
|
+
return {
|
|
230
|
+
value: name,
|
|
231
|
+
label: name,
|
|
232
|
+
hint: auto ? def.hintWhenAuto : def.hintWhenManual,
|
|
233
|
+
};
|
|
234
|
+
})
|
|
235
|
+
.filter(Boolean);
|
|
236
|
+
const initialValues = pickConditionalAuto(features, agents, profileName);
|
|
180
237
|
|
|
181
238
|
if (conditionalOptions.length > 0) {
|
|
182
239
|
const picked = await p.multiselect({
|
|
@@ -194,14 +251,17 @@ export async function initCommand(opts) {
|
|
|
194
251
|
optedSkills = [];
|
|
195
252
|
}
|
|
196
253
|
|
|
197
|
-
const
|
|
254
|
+
const universalForProfile = requiredSkillsForProfile(profileName);
|
|
255
|
+
const totalCount = universalForProfile.length + optedSkills.length;
|
|
198
256
|
const optedSummary = optedSkills.length
|
|
199
257
|
? `, plus ${optedSkills.join(', ')}`
|
|
200
258
|
: '';
|
|
201
259
|
const confirm = await p.confirm({
|
|
202
|
-
message: `Install ${totalCount} skill${
|
|
203
|
-
'
|
|
204
|
-
)}${optedSummary}) for ${agents
|
|
260
|
+
message: `Install ${totalCount} skill${
|
|
261
|
+
totalCount === 1 ? '' : 's'
|
|
262
|
+
} (${universalForProfile.join(', ')}${optedSummary}) for ${agents
|
|
263
|
+
.map((a) => AGENT_LABEL[a])
|
|
264
|
+
.join(' + ')}?`,
|
|
205
265
|
initialValue: true,
|
|
206
266
|
});
|
|
207
267
|
if (p.isCancel(confirm) || !confirm) {
|
|
@@ -210,7 +270,7 @@ export async function initCommand(opts) {
|
|
|
210
270
|
}
|
|
211
271
|
} else {
|
|
212
272
|
agents = resolveAgents(opts.agent, detectedAgents);
|
|
213
|
-
optedSkills = pickConditionalAuto(features, agents);
|
|
273
|
+
optedSkills = pickConditionalAuto(features, agents, profileName);
|
|
214
274
|
}
|
|
215
275
|
|
|
216
276
|
const confirmReplace = interactive
|
|
@@ -224,7 +284,7 @@ export async function initCommand(opts) {
|
|
|
224
284
|
const allActions = [];
|
|
225
285
|
const installedSkillSet = new Set();
|
|
226
286
|
for (const agent of agents) {
|
|
227
|
-
const agentSkills = skillsForAgent(agent, optedSkills);
|
|
287
|
+
const agentSkills = skillsForAgent(agent, profileName, optedSkills);
|
|
228
288
|
for (const s of agentSkills) installedSkillSet.add(s);
|
|
229
289
|
const previousStates = { [agent]: loadState(cwd, agent) };
|
|
230
290
|
const { actions, nextStates } = await installSkills({
|
|
@@ -236,7 +296,9 @@ export async function initCommand(opts) {
|
|
|
236
296
|
kitVersion: pkg.version,
|
|
237
297
|
});
|
|
238
298
|
allActions.push(...actions);
|
|
239
|
-
|
|
299
|
+
const next = nextStates[agent];
|
|
300
|
+
next.profile = profileName;
|
|
301
|
+
saveState(cwd, agent, next);
|
|
240
302
|
}
|
|
241
303
|
|
|
242
304
|
const skillDisplayOrder = [
|
|
@@ -283,9 +345,30 @@ export async function initCommand(opts) {
|
|
|
283
345
|
: []),
|
|
284
346
|
...(optedSkills.includes('agentic-skill') ? ['/agentic-skill'] : []),
|
|
285
347
|
...(optedSkills.includes('agentic-hooks') ? ['/agentic-hooks (WORKFLOW §11)'] : []),
|
|
286
|
-
]
|
|
348
|
+
]
|
|
349
|
+
.filter((line) => {
|
|
350
|
+
// Filter the universal-set entries to only those actually installed
|
|
351
|
+
// for this profile.
|
|
352
|
+
const universalNames = requiredSkillsForProfile(profileName);
|
|
353
|
+
const universalLabels = {
|
|
354
|
+
'agentic-bootstrap': '/agentic-bootstrap (AGENTS.md)',
|
|
355
|
+
'agentic-architecture': '/agentic-architecture (ARCHITECTURE.md)',
|
|
356
|
+
'agentic-adr': '/agentic-adr',
|
|
357
|
+
'agentic-spec': '/agentic-spec (doc/specs/)',
|
|
358
|
+
'agentic-task': '/agentic-task',
|
|
359
|
+
'agentic-audit': '/agentic-audit',
|
|
360
|
+
'agentic-review': '/agentic-review (WORKFLOW §10)',
|
|
361
|
+
'agentic-ground': '/agentic-ground (WORKFLOW §4 + §5)',
|
|
362
|
+
// 'agentic-philosophy' is implicit and not listed.
|
|
363
|
+
};
|
|
364
|
+
for (const [skill, label] of Object.entries(universalLabels)) {
|
|
365
|
+
if (line === label) return universalNames.includes(skill);
|
|
366
|
+
}
|
|
367
|
+
return true;
|
|
368
|
+
})
|
|
369
|
+
.join(', ');
|
|
287
370
|
p.outro(
|
|
288
|
-
`Done. In ${agents
|
|
371
|
+
`Done (profile: ${profileName}). In ${agents
|
|
289
372
|
.map((a) => AGENT_LABEL[a])
|
|
290
373
|
.join(' or ')}: ${slashLine}. agentic-philosophy auto-loads on non-trivial work.`
|
|
291
374
|
);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { detectAgents } from '../lib/detect.js';
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_PROFILE,
|
|
5
|
+
PROFILES,
|
|
6
|
+
PROFILE_NAMES,
|
|
7
|
+
validateProfile,
|
|
8
|
+
} from '../lib/profiles.js';
|
|
9
|
+
import { loadState, saveState } from '../lib/state.js';
|
|
10
|
+
import { updateCommand } from './update.js';
|
|
11
|
+
|
|
12
|
+
const VALID_AGENTS = ['claude-code', 'codex'];
|
|
13
|
+
|
|
14
|
+
const AGENT_LABEL = {
|
|
15
|
+
'claude-code': 'Claude Code',
|
|
16
|
+
codex: 'Codex',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function readProjectProfile(cwd) {
|
|
20
|
+
const perAgent = {};
|
|
21
|
+
for (const agent of VALID_AGENTS) {
|
|
22
|
+
const state = loadState(cwd, agent);
|
|
23
|
+
if (state) perAgent[agent] = state.profile ?? DEFAULT_PROFILE;
|
|
24
|
+
}
|
|
25
|
+
return perAgent;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function showProfile(cwd) {
|
|
29
|
+
const perAgent = readProjectProfile(cwd);
|
|
30
|
+
if (Object.keys(perAgent).length === 0) {
|
|
31
|
+
process.stdout.write(
|
|
32
|
+
'No agentic install detected (no .claude/agentic-state.json or .agents/agentic-state.json).\n'
|
|
33
|
+
);
|
|
34
|
+
process.stdout.write(
|
|
35
|
+
`Run \`agentic init\` to scaffold; default profile is \`${DEFAULT_PROFILE}\`.\n`
|
|
36
|
+
);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
for (const [agent, profile] of Object.entries(perAgent)) {
|
|
40
|
+
process.stdout.write(`${AGENT_LABEL[agent]}: ${profile}\n`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function listProfiles() {
|
|
45
|
+
for (const name of PROFILE_NAMES) {
|
|
46
|
+
const def = PROFILES[name];
|
|
47
|
+
process.stdout.write(`${name}\n ${def.note}\n`);
|
|
48
|
+
process.stdout.write(` Universal: ${def.universal.join(', ')}\n`);
|
|
49
|
+
const conditional = Object.entries(def.conditional)
|
|
50
|
+
.filter(([, rule]) => rule !== 'blocked')
|
|
51
|
+
.map(([skill, rule]) => `${skill}=${formatRule(rule)}`)
|
|
52
|
+
.join(', ');
|
|
53
|
+
process.stdout.write(` Conditional: ${conditional || 'none'}\n\n`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function formatRule(rule) {
|
|
58
|
+
if (rule === true) return 'recommended';
|
|
59
|
+
if (rule === false) return 'opt-in';
|
|
60
|
+
if (typeof rule === 'string') return `auto-if-${rule}`;
|
|
61
|
+
return String(rule);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function setProfile(cwd, name, opts) {
|
|
65
|
+
validateProfile(name);
|
|
66
|
+
|
|
67
|
+
const perAgent = readProjectProfile(cwd);
|
|
68
|
+
if (Object.keys(perAgent).length === 0) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'no agentic install detected. Run `agentic init --profile <name>` first.'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const interactive = process.stdout.isTTY && !opts.yes;
|
|
75
|
+
|
|
76
|
+
const currentProfiles = [...new Set(Object.values(perAgent))];
|
|
77
|
+
if (currentProfiles.length === 1 && currentProfiles[0] === name) {
|
|
78
|
+
process.stdout.write(`Profile already \`${name}\` for all installed agents. No change.\n`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (interactive) {
|
|
83
|
+
p.intro(`agentic profile set ${name}`);
|
|
84
|
+
p.note(
|
|
85
|
+
Object.entries(perAgent)
|
|
86
|
+
.map(([agent, profile]) => `${AGENT_LABEL[agent]}: ${profile} → ${name}`)
|
|
87
|
+
.join('\n'),
|
|
88
|
+
'Profile change'
|
|
89
|
+
);
|
|
90
|
+
p.note(PROFILES[name].note, `${name} profile`);
|
|
91
|
+
|
|
92
|
+
const confirm = await p.confirm({
|
|
93
|
+
message:
|
|
94
|
+
'This may add or remove skills under the new profile. Continue? (`agentic update` runs after.)',
|
|
95
|
+
initialValue: true,
|
|
96
|
+
});
|
|
97
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
98
|
+
p.cancel('Cancelled.');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Write the new profile to each installed agent's state file before
|
|
104
|
+
// running update, so update reads the new profile.
|
|
105
|
+
for (const agent of Object.keys(perAgent)) {
|
|
106
|
+
const state = loadState(cwd, agent);
|
|
107
|
+
state.profile = name;
|
|
108
|
+
saveState(cwd, agent, state);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Run the equivalent of `agentic update` so the install set matches the
|
|
112
|
+
// new profile. The update command picks profile up from the state files
|
|
113
|
+
// we just wrote.
|
|
114
|
+
await updateCommand({
|
|
115
|
+
agent: undefined,
|
|
116
|
+
yes: opts.yes,
|
|
117
|
+
dryRun: false,
|
|
118
|
+
force: false,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (interactive) {
|
|
122
|
+
p.outro(`Profile set to \`${name}\`. Install state refreshed.`);
|
|
123
|
+
} else {
|
|
124
|
+
process.stdout.write(`Profile set to \`${name}\`. Install state refreshed.\n`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function profileCommand(arg, opts) {
|
|
129
|
+
const cwd = process.cwd();
|
|
130
|
+
|
|
131
|
+
if (!arg) {
|
|
132
|
+
showProfile(cwd);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (arg === 'list') {
|
|
137
|
+
listProfiles();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (arg === 'show') {
|
|
142
|
+
showProfile(cwd);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (arg === 'set') {
|
|
147
|
+
if (!opts.name) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
'profile set requires a profile name. Usage: `agentic profile set <name>`'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
await setProfile(cwd, opts.name, opts);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// If `arg` is not a subcommand, treat it as `set <name>` shorthand.
|
|
157
|
+
if (PROFILE_NAMES.includes(arg)) {
|
|
158
|
+
await setProfile(cwd, arg, opts);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
throw new Error(
|
|
163
|
+
`unknown subcommand "${arg}". Use \`show\`, \`list\`, or \`set <name>\` (or pass a profile name directly).`
|
|
164
|
+
);
|
|
165
|
+
}
|
package/src/commands/update.js
CHANGED
|
@@ -5,9 +5,27 @@ import { dirname, join } from 'node:path';
|
|
|
5
5
|
import { detectAgents, detectFeatures } from '../lib/detect.js';
|
|
6
6
|
import { installSkills, removeOrphanSkills } from '../lib/install.js';
|
|
7
7
|
import { loadState, saveState } from '../lib/state.js';
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_PROFILE,
|
|
10
|
+
availableConditionalsForProfile,
|
|
11
|
+
profileOrDefault,
|
|
12
|
+
requiredSkillsForProfile,
|
|
13
|
+
} from '../lib/profiles.js';
|
|
8
14
|
import { updateRootDoc } from '../lib/rootdoc.js';
|
|
9
15
|
import { CONDITIONAL_SKILLS, REQUIRED_SKILLS } from './init.js';
|
|
10
16
|
|
|
17
|
+
const CONDITIONAL_BY_NAME = Object.fromEntries(
|
|
18
|
+
CONDITIONAL_SKILLS.map((s) => [s.name, s])
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
function evaluateRule(rule, features, targetAgents) {
|
|
22
|
+
if (rule === 'frontend') return features.frontend === true;
|
|
23
|
+
if (rule === 'claude-code') return targetAgents.includes('claude-code');
|
|
24
|
+
if (rule === true) return true;
|
|
25
|
+
if (rule === false) return false;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
30
|
const pkg = JSON.parse(
|
|
13
31
|
readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf8')
|
|
@@ -48,18 +66,24 @@ function resolveAgents(flagValue, detectedAgents, previousAgents) {
|
|
|
48
66
|
return ['claude-code'];
|
|
49
67
|
}
|
|
50
68
|
|
|
51
|
-
function pickConditionalAuto(features, targetAgents) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
function pickConditionalAuto(features, targetAgents, profileName) {
|
|
70
|
+
const out = [];
|
|
71
|
+
for (const { name, rule } of availableConditionalsForProfile(profileName)) {
|
|
72
|
+
const def = CONDITIONAL_BY_NAME[name];
|
|
73
|
+
if (!def) continue;
|
|
74
|
+
if (!def.agents.some((a) => targetAgents.includes(a))) continue;
|
|
75
|
+
if (evaluateRule(rule, features, targetAgents)) out.push(name);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
55
78
|
}
|
|
56
79
|
|
|
57
|
-
function skillsForAgent(agent, optedSkills) {
|
|
80
|
+
function skillsForAgent(agent, profileName, optedSkills) {
|
|
81
|
+
const universal = requiredSkillsForProfile(profileName);
|
|
58
82
|
const conditional = optedSkills.filter((skillName) => {
|
|
59
|
-
const def =
|
|
83
|
+
const def = CONDITIONAL_BY_NAME[skillName];
|
|
60
84
|
return def && def.agents.includes(agent);
|
|
61
85
|
});
|
|
62
|
-
return [...
|
|
86
|
+
return [...universal, ...conditional];
|
|
63
87
|
}
|
|
64
88
|
|
|
65
89
|
function previousAgentsFromStates(cwd) {
|
|
@@ -71,13 +95,19 @@ function previousAgentsFromStates(cwd) {
|
|
|
71
95
|
return out;
|
|
72
96
|
}
|
|
73
97
|
|
|
74
|
-
function previouslyOptedConditional(previousStates, currentAgents) {
|
|
98
|
+
function previouslyOptedConditional(previousStates, currentAgents, profileName) {
|
|
99
|
+
const available = new Set(
|
|
100
|
+
availableConditionalsForProfile(profileName).map((c) => c.name)
|
|
101
|
+
);
|
|
75
102
|
const opted = new Set();
|
|
76
103
|
for (const agent of currentAgents) {
|
|
77
104
|
const prev = previousStates[agent];
|
|
78
105
|
if (!prev) continue;
|
|
79
106
|
for (const skill of Object.keys(prev.skills ?? {})) {
|
|
80
|
-
if (
|
|
107
|
+
if (
|
|
108
|
+
CONDITIONAL_BY_NAME[skill] &&
|
|
109
|
+
available.has(skill)
|
|
110
|
+
) {
|
|
81
111
|
opted.add(skill);
|
|
82
112
|
}
|
|
83
113
|
}
|
|
@@ -85,6 +115,23 @@ function previouslyOptedConditional(previousStates, currentAgents) {
|
|
|
85
115
|
return [...opted];
|
|
86
116
|
}
|
|
87
117
|
|
|
118
|
+
function profileFromStates(previousStates, currentAgents) {
|
|
119
|
+
// If multiple agents disagree on profile, surface and bail. Profile is
|
|
120
|
+
// expected to match across agents in the same project.
|
|
121
|
+
const seen = new Set();
|
|
122
|
+
for (const agent of currentAgents) {
|
|
123
|
+
const prev = previousStates[agent];
|
|
124
|
+
if (prev?.profile) seen.add(prev.profile);
|
|
125
|
+
}
|
|
126
|
+
if (seen.size === 0) return DEFAULT_PROFILE;
|
|
127
|
+
if (seen.size > 1) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`state files disagree on profile (${[...seen].join(', ')}). Run \`agentic profile set <name>\` to reconcile.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return [...seen][0];
|
|
133
|
+
}
|
|
134
|
+
|
|
88
135
|
export async function updateCommand(opts) {
|
|
89
136
|
if (opts.agent && !AGENT_FLAG_VALUES.includes(opts.agent)) {
|
|
90
137
|
throw new Error(
|
|
@@ -107,8 +154,13 @@ export async function updateCommand(opts) {
|
|
|
107
154
|
previousStates[agent] = loadState(cwd, agent);
|
|
108
155
|
}
|
|
109
156
|
|
|
110
|
-
const
|
|
111
|
-
const
|
|
157
|
+
const profileName = profileFromStates(previousStates, agents);
|
|
158
|
+
const previousOpted = previouslyOptedConditional(
|
|
159
|
+
previousStates,
|
|
160
|
+
agents,
|
|
161
|
+
profileName
|
|
162
|
+
);
|
|
163
|
+
const autoOpted = pickConditionalAuto(features, agents, profileName);
|
|
112
164
|
const defaultOpted = previousOpted.length ? previousOpted : autoOpted;
|
|
113
165
|
|
|
114
166
|
let optedSkills;
|
|
@@ -120,17 +172,24 @@ export async function updateCommand(opts) {
|
|
|
120
172
|
p.note(
|
|
121
173
|
`Previous install: ${previousLine}\n` +
|
|
122
174
|
`Updating for: ${agents.map((a) => AGENT_LABEL[a]).join(' + ')}\n` +
|
|
175
|
+
`Profile: ${profileName}\n` +
|
|
123
176
|
`Kit version: ${pkg.version}`,
|
|
124
177
|
'Update plan'
|
|
125
178
|
);
|
|
126
179
|
|
|
127
|
-
const conditionalOptions =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
180
|
+
const conditionalOptions = availableConditionalsForProfile(profileName)
|
|
181
|
+
.map(({ name, rule }) => {
|
|
182
|
+
const def = CONDITIONAL_BY_NAME[name];
|
|
183
|
+
if (!def) return null;
|
|
184
|
+
if (!def.agents.some((a) => agents.includes(a))) return null;
|
|
185
|
+
const auto = evaluateRule(rule, features, agents);
|
|
186
|
+
return {
|
|
187
|
+
value: name,
|
|
188
|
+
label: name,
|
|
189
|
+
hint: auto ? def.hintWhenAuto : def.hintWhenManual,
|
|
190
|
+
};
|
|
191
|
+
})
|
|
192
|
+
.filter(Boolean);
|
|
134
193
|
|
|
135
194
|
if (conditionalOptions.length > 0) {
|
|
136
195
|
const picked = await p.multiselect({
|
|
@@ -172,7 +231,7 @@ export async function updateCommand(opts) {
|
|
|
172
231
|
const nextStates = {};
|
|
173
232
|
|
|
174
233
|
for (const agent of agents) {
|
|
175
|
-
const agentSkills = skillsForAgent(agent, optedSkills);
|
|
234
|
+
const agentSkills = skillsForAgent(agent, profileName, optedSkills);
|
|
176
235
|
for (const s of agentSkills) installedSkillSet.add(s);
|
|
177
236
|
|
|
178
237
|
const orphanResult = await removeOrphanSkills({
|
|
@@ -196,7 +255,9 @@ export async function updateCommand(opts) {
|
|
|
196
255
|
force,
|
|
197
256
|
});
|
|
198
257
|
allActions.push(...result.actions);
|
|
199
|
-
|
|
258
|
+
const next = result.nextStates[agent];
|
|
259
|
+
next.profile = profileName;
|
|
260
|
+
nextStates[agent] = next;
|
|
200
261
|
}
|
|
201
262
|
|
|
202
263
|
if (!dryRun) {
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { initCommand } from './commands/init.js';
|
|
6
6
|
import { updateCommand } from './commands/update.js';
|
|
7
|
+
import { profileCommand } from './commands/profile.js';
|
|
7
8
|
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const pkg = JSON.parse(
|
|
@@ -22,6 +23,7 @@ export async function run(argv) {
|
|
|
22
23
|
.command('init')
|
|
23
24
|
.description('Install agentic skills into this project for Claude Code and/or Codex')
|
|
24
25
|
.option('-a, --agent <agent>', 'install for a specific agent: claude-code | codex | both')
|
|
26
|
+
.option('-p, --profile <profile>', 'project maturity profile: poc | solo | team | mature (default: team)')
|
|
25
27
|
.option('-y, --yes', 'skip confirmation prompts (non-interactive)')
|
|
26
28
|
.action(initCommand);
|
|
27
29
|
|
|
@@ -34,5 +36,12 @@ export async function run(argv) {
|
|
|
34
36
|
.option('--force', 'overwrite user-edited files on conflict (non-interactive default: no)')
|
|
35
37
|
.action(updateCommand);
|
|
36
38
|
|
|
39
|
+
program
|
|
40
|
+
.command('profile [subcommand]')
|
|
41
|
+
.description('Show, list, or set the project maturity profile (poc | solo | team | mature)')
|
|
42
|
+
.option('-n, --name <name>', 'profile name when used with `set` subcommand')
|
|
43
|
+
.option('-y, --yes', 'skip confirmation prompts (non-interactive)')
|
|
44
|
+
.action(profileCommand);
|
|
45
|
+
|
|
37
46
|
await program.parseAsync(argv);
|
|
38
47
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project maturity profiles per ADR-0013.
|
|
3
|
+
*
|
|
4
|
+
* Each profile declares:
|
|
5
|
+
* - `universal`: skills that always install for this profile.
|
|
6
|
+
* - `conditional`: per-conditional-skill rules. `autoIf` is one of:
|
|
7
|
+
* - a feature predicate name from detectFeatures (`'frontend'`, `'claude-code'`)
|
|
8
|
+
* - `true` — recommended (pre-checked in TUI; auto-installed in non-interactive)
|
|
9
|
+
* - `false` — allowed but not recommended (un-checked in TUI; not auto-installed)
|
|
10
|
+
* - `'blocked'` — not available at this profile (omitted from TUI options)
|
|
11
|
+
* - `note`: human-readable summary shown in the profile-selection TUI and
|
|
12
|
+
* in `agentic profile list`.
|
|
13
|
+
*
|
|
14
|
+
* Profiles are monotone supersets: poc ⊆ solo ⊆ team ⊆ mature.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_PROFILE = 'team';
|
|
18
|
+
|
|
19
|
+
export const PROFILE_NAMES = ['poc', 'solo', 'team', 'mature'];
|
|
20
|
+
|
|
21
|
+
export const PROFILES = {
|
|
22
|
+
poc: {
|
|
23
|
+
universal: ['agentic-philosophy', 'agentic-ground', 'agentic-audit'],
|
|
24
|
+
conditional: {
|
|
25
|
+
'agentic-design': 'blocked',
|
|
26
|
+
'agentic-subagent': 'blocked',
|
|
27
|
+
'agentic-skill': 'blocked',
|
|
28
|
+
'agentic-hooks': 'blocked',
|
|
29
|
+
},
|
|
30
|
+
note: 'PoC / spike / experiment. Posture (philosophy) + research (ground) + drift (audit). No mandatory artifact-producing skills. Adds discipline you can grow into; never pre-imposes ceremony.',
|
|
31
|
+
},
|
|
32
|
+
solo: {
|
|
33
|
+
universal: [
|
|
34
|
+
'agentic-philosophy',
|
|
35
|
+
'agentic-ground',
|
|
36
|
+
'agentic-audit',
|
|
37
|
+
'agentic-bootstrap',
|
|
38
|
+
'agentic-spec',
|
|
39
|
+
'agentic-task',
|
|
40
|
+
'agentic-review',
|
|
41
|
+
],
|
|
42
|
+
conditional: {
|
|
43
|
+
'agentic-architecture': false,
|
|
44
|
+
'agentic-adr': false,
|
|
45
|
+
'agentic-design': 'frontend',
|
|
46
|
+
'agentic-subagent': 'claude-code',
|
|
47
|
+
'agentic-skill': false,
|
|
48
|
+
'agentic-hooks': false,
|
|
49
|
+
},
|
|
50
|
+
note: 'Solo developer shipping a real product. Specs and tasks are universal; ADRs and architecture are opt-in for binding decisions only.',
|
|
51
|
+
},
|
|
52
|
+
team: {
|
|
53
|
+
universal: [
|
|
54
|
+
'agentic-bootstrap',
|
|
55
|
+
'agentic-philosophy',
|
|
56
|
+
'agentic-architecture',
|
|
57
|
+
'agentic-adr',
|
|
58
|
+
'agentic-spec',
|
|
59
|
+
'agentic-task',
|
|
60
|
+
'agentic-audit',
|
|
61
|
+
'agentic-review',
|
|
62
|
+
'agentic-ground',
|
|
63
|
+
],
|
|
64
|
+
conditional: {
|
|
65
|
+
'agentic-design': 'frontend',
|
|
66
|
+
'agentic-subagent': 'claude-code',
|
|
67
|
+
'agentic-skill': false,
|
|
68
|
+
'agentic-hooks': false,
|
|
69
|
+
},
|
|
70
|
+
note: 'Team product. Full universal stack; conditional skills auto-detect by signal. This was the v0.7 default and is the migration target for existing installs.',
|
|
71
|
+
},
|
|
72
|
+
mature: {
|
|
73
|
+
universal: [
|
|
74
|
+
'agentic-bootstrap',
|
|
75
|
+
'agentic-philosophy',
|
|
76
|
+
'agentic-architecture',
|
|
77
|
+
'agentic-adr',
|
|
78
|
+
'agentic-spec',
|
|
79
|
+
'agentic-task',
|
|
80
|
+
'agentic-audit',
|
|
81
|
+
'agentic-review',
|
|
82
|
+
'agentic-ground',
|
|
83
|
+
],
|
|
84
|
+
conditional: {
|
|
85
|
+
'agentic-design': 'frontend',
|
|
86
|
+
'agentic-subagent': 'claude-code',
|
|
87
|
+
'agentic-skill': false,
|
|
88
|
+
'agentic-hooks': true,
|
|
89
|
+
},
|
|
90
|
+
note: 'Mature / regulated product. Recommends agentic-hooks alongside the team stack for deterministic gates per WORKFLOW §11. Future evals + spike skills land here when shipped.',
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export function validateProfile(name) {
|
|
95
|
+
if (!PROFILE_NAMES.includes(name)) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`unknown profile "${name}". Valid: ${PROFILE_NAMES.join(', ')}`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return name;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function profileOrDefault(name) {
|
|
104
|
+
if (!name) return DEFAULT_PROFILE;
|
|
105
|
+
return validateProfile(name);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function requiredSkillsForProfile(name) {
|
|
109
|
+
return [...PROFILES[validateProfile(name)].universal];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function conditionalRulesForProfile(name) {
|
|
113
|
+
return { ...PROFILES[validateProfile(name)].conditional };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Returns the conditional skills available at this profile, with their
|
|
118
|
+
* effective `autoIf` posture. Skipped (blocked) skills are omitted.
|
|
119
|
+
*/
|
|
120
|
+
export function availableConditionalsForProfile(profileName) {
|
|
121
|
+
const rules = conditionalRulesForProfile(profileName);
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const [skill, rule] of Object.entries(rules)) {
|
|
124
|
+
if (rule === 'blocked') continue;
|
|
125
|
+
out.push({ name: skill, rule });
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
package/src/lib/state.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
+
import { DEFAULT_PROFILE, validateProfile } from './profiles.js';
|
|
3
4
|
|
|
4
5
|
export const SCHEMA_VERSION = 1;
|
|
5
6
|
export const STATE_FILE = 'agentic-state.json';
|
|
@@ -15,11 +16,12 @@ export function statePath(cwd, agent) {
|
|
|
15
16
|
return join(cwd, dir, STATE_FILE);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
export function emptyState(agent, kitVersion) {
|
|
19
|
+
export function emptyState(agent, kitVersion, profile = DEFAULT_PROFILE) {
|
|
19
20
|
return {
|
|
20
21
|
schemaVersion: SCHEMA_VERSION,
|
|
21
22
|
kitVersion,
|
|
22
23
|
agent,
|
|
24
|
+
profile: validateProfile(profile),
|
|
23
25
|
skills: {},
|
|
24
26
|
};
|
|
25
27
|
}
|
|
@@ -46,10 +48,23 @@ export function loadState(cwd, agent) {
|
|
|
46
48
|
`state at ${path} declares agent "${raw.agent}" but expected "${agent}"`
|
|
47
49
|
);
|
|
48
50
|
}
|
|
51
|
+
// `profile` is optional and forward-compatible. Pre-v0.8 state files have
|
|
52
|
+
// no profile field; default to `team` per ADR-0013 (the v0.7 install set is
|
|
53
|
+
// byte-identical to the team profile).
|
|
54
|
+
let profile = DEFAULT_PROFILE;
|
|
55
|
+
if (raw.profile) {
|
|
56
|
+
try {
|
|
57
|
+
profile = validateProfile(raw.profile);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw new Error(`state at ${path}: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
49
63
|
return {
|
|
50
64
|
schemaVersion: raw.schemaVersion,
|
|
51
65
|
kitVersion: raw.kitVersion ?? null,
|
|
52
66
|
agent,
|
|
67
|
+
profile,
|
|
53
68
|
skills: raw.skills ?? {},
|
|
54
69
|
};
|
|
55
70
|
}
|
|
@@ -75,6 +90,7 @@ function orderState(state) {
|
|
|
75
90
|
schemaVersion: state.schemaVersion,
|
|
76
91
|
kitVersion: state.kitVersion,
|
|
77
92
|
agent: state.agent,
|
|
93
|
+
profile: state.profile ?? DEFAULT_PROFILE,
|
|
78
94
|
skills,
|
|
79
95
|
};
|
|
80
96
|
}
|
|
@@ -24,6 +24,10 @@ Use the template below. Fill `Context`, `Decision`, `Consequences`, and `Alterna
|
|
|
24
24
|
* `Consequences` lists positive and negative; do not balance for the sake of balance.
|
|
25
25
|
* `Alternatives Considered` lists each rejected option with a one-line reason.
|
|
26
26
|
|
|
27
|
+
## Interview UX
|
|
28
|
+
|
|
29
|
+
When the host exposes `AskUserQuestion`, use it for multi-choice prompts (`Status: proposed / accepted / deprecated / superseded`, decider role selection, alternative-option ranking) and for confirmation gates with non-trivial branching. Inline text questions are the fallback only when the host lacks a structured-prompt primitive (Codex). One card per multi-choice gate beats chained text questions.
|
|
30
|
+
|
|
27
31
|
## Step 4 — Write the file
|
|
28
32
|
|
|
29
33
|
Path: `doc/adr/<NNNN>-<short-slug>.md`. Slug: kebab-case, ≤6 words, derived from the decision title. Status: `proposed`. Date: today, ISO format. Deciders: ask the user.
|
|
@@ -42,6 +42,8 @@ Print to the user:
|
|
|
42
42
|
|
|
43
43
|
One question per gap.
|
|
44
44
|
|
|
45
|
+
When the host exposes Plan Mode, the agent may render the proposed `ARCHITECTURE.md` body inside the plan for user approval before write. Plan Mode is opt-in — skip for incremental edits where the user already saw the prior content.
|
|
46
|
+
|
|
45
47
|
## Step 4 — Write the file
|
|
46
48
|
|
|
47
49
|
On user confirmation, write `ARCHITECTURE.md` at the repo root. Cut every line that does not lock a binding pattern. At the end of the response, list any decision that should become an ADR — flag, do not write the ADR yet (use the `agentic-adr` skill for that).
|
|
@@ -44,6 +44,8 @@ Print to the user:
|
|
|
44
44
|
|
|
45
45
|
One question per gap. Skip everything filled confidently. Do **not** ask philosophical questions ("is this doc for agents or humans?", "what is the most important quality bar?") — those are decisions, not interview material.
|
|
46
46
|
|
|
47
|
+
When the host exposes Plan Mode, the agent may render the proposed `AGENTS.md` body inside the plan (the user reviews and approves before write) instead of writing-then-presenting. Plan Mode is opt-in — skip for incremental edits where the user already saw the prior content.
|
|
48
|
+
|
|
47
49
|
## Step 4 — Write the file
|
|
48
50
|
|
|
49
51
|
On user confirmation, write `AGENTS.md` at the repo root. Cut every line that does not change agent behavior. No "External Resources" section (URLs are derivable from `git remote` and the manifest). No appended Universal Agent Behavior block. No marketing prose.
|
|
@@ -55,6 +55,8 @@ Before handing off to implementation, report a soft verdict against four checks:
|
|
|
55
55
|
|
|
56
56
|
If any check fails, surface the gap to the user and ask before proceeding rather than blocking. The user retains the authority to skip; the discipline is in surfacing, not in enforcement.
|
|
57
57
|
|
|
58
|
+
When the host exposes `AskUserQuestion`, render the checkpoint as a structured multi-choice card listing the six checks with their yes/no/n.a. status plus a final `proceed / pause for more research` selector — instead of dropping the verdict as plain text. Falls back to numbered text on hosts without the primitive (Codex).
|
|
59
|
+
|
|
58
60
|
## Output contract
|
|
59
61
|
|
|
60
62
|
A single message structured as:
|
|
@@ -18,6 +18,8 @@ Confirm what to review. Default scopes, in priority order:
|
|
|
18
18
|
|
|
19
19
|
If no diff exists, stop and tell the user — there's nothing to review.
|
|
20
20
|
|
|
21
|
+
When the host exposes `AskUserQuestion`, use it at Step 0 to confirm the review scope as a multi-choice card (`branch vs main / PR#NN / commit-sha / working-tree`) instead of asking inline text. Falls back to numbered text on hosts without the primitive (Codex).
|
|
22
|
+
|
|
21
23
|
## Step 1 — Assemble the handoff
|
|
22
24
|
|
|
23
25
|
The reviewer subagent will get **only** what you assemble here. No conversation history, no prior context.
|
|
@@ -32,6 +32,10 @@ Ask one question per missing field, in this order. Skip the philosophical questi
|
|
|
32
32
|
|
|
33
33
|
Status starts at `draft`. Created: today, ISO format. Owner: ask. Do **not** invent values — when the user does not know, leave `<TODO>` and ask.
|
|
34
34
|
|
|
35
|
+
## Interview UX
|
|
36
|
+
|
|
37
|
+
When the host exposes `AskUserQuestion`, use it for multi-choice prompts (`Status: draft / accepted / shipped`, owner selection from team members, scope-vs-multiple-features confirmation) and for confirmation gates with non-trivial branching. Inline text questions are an acceptable fallback only when the host lacks a structured-prompt primitive (Codex). One question per gate; do not chain three text questions when one `AskUserQuestion` card lists the options.
|
|
38
|
+
|
|
35
39
|
## Step 4 — Write the file
|
|
36
40
|
|
|
37
41
|
Path: `doc/specs/<NNNN>-<short-slug>.md`. Use the template below.
|
|
@@ -27,6 +27,10 @@ Status starts at `proposed`. Created: today, ISO format. Notes: empty (filled du
|
|
|
27
27
|
|
|
28
28
|
**Do not invent values.** When the user does not know something, leave `<TODO>` and ask. Stop after writing the file — do not start work.
|
|
29
29
|
|
|
30
|
+
## Interview UX
|
|
31
|
+
|
|
32
|
+
When the host exposes `AskUserQuestion`, use it for multi-choice prompts (status, owner selection, Spec-ref pick from existing `doc/specs/`) and for confirmation gates with non-trivial branching. Inline text questions are the fallback only when the host lacks a structured-prompt primitive (Codex). Single card per multi-choice gate beats chained text questions.
|
|
33
|
+
|
|
30
34
|
## Step 3 — Write the file
|
|
31
35
|
|
|
32
36
|
Path: `doc/tasks/<NNNN>-<short-slug>.md`. Use the template below.
|