@alexandrealvaro/agentic 0.11.2-beta.1 → 0.11.3-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/package.json +1 -1
- package/src/commands/init.js +34 -5
- package/src/commands/profile.js +32 -7
- package/src/commands/update.js +33 -14
- package/src/index.js +16 -3
- package/src/lib/install.js +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexandrealvaro/agentic",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3-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": {
|
package/src/commands/init.js
CHANGED
|
@@ -94,6 +94,28 @@ export const CONDITIONAL_SKILLS = [
|
|
|
94
94
|
hintWhenAuto: 'opt-in',
|
|
95
95
|
hintWhenManual: 'WORKFLOW §11 hooks scaffolder (pre-commit, pre-push)',
|
|
96
96
|
},
|
|
97
|
+
// The next two skills are universal in `team` / `mature` profiles
|
|
98
|
+
// (declared in PROFILES['team' / 'mature'].universal in src/lib/profiles.js)
|
|
99
|
+
// and conditional/opt-in in `solo`. They must appear in this catalog so
|
|
100
|
+
// `availableConditionalsForProfile('solo')` lookups in `pickConditionalAuto`
|
|
101
|
+
// succeed — without these entries, `if (!def) continue` silently skipped
|
|
102
|
+
// them and a `solo` user could not opt-in to either (review B1, v0.11.3).
|
|
103
|
+
// The autoIf rule here is the universal-default; per-profile overrides
|
|
104
|
+
// come from `availableConditionalsForProfile`'s rule field.
|
|
105
|
+
{
|
|
106
|
+
name: 'agentic-architecture',
|
|
107
|
+
autoIf: () => true,
|
|
108
|
+
agents: ['claude-code', 'codex'],
|
|
109
|
+
hintWhenAuto: 'system patterns + boundaries',
|
|
110
|
+
hintWhenManual: 'opt-in (recommended once load-bearing patterns emerge)',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'agentic-adr',
|
|
114
|
+
autoIf: () => true,
|
|
115
|
+
agents: ['claude-code', 'codex'],
|
|
116
|
+
hintWhenAuto: 'binding architectural decisions (Nygard pattern)',
|
|
117
|
+
hintWhenManual: 'opt-in (recommended for binding decisions worth recording)',
|
|
118
|
+
},
|
|
97
119
|
];
|
|
98
120
|
|
|
99
121
|
const CONDITIONAL_BY_NAME = Object.fromEntries(
|
|
@@ -295,16 +317,23 @@ export async function initCommand(opts) {
|
|
|
295
317
|
confirmReplace,
|
|
296
318
|
previousStates,
|
|
297
319
|
kitVersion: pkg.version,
|
|
320
|
+
profile: profileName,
|
|
298
321
|
});
|
|
299
322
|
allActions.push(...actions);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
saveState(cwd, agent,
|
|
323
|
+
// installSkills now stamps `profile` into nextStates per review C3.
|
|
324
|
+
// No post-hoc injection.
|
|
325
|
+
saveState(cwd, agent, nextStates[agent]);
|
|
303
326
|
}
|
|
304
327
|
|
|
328
|
+
// Dedup: agentic-architecture and agentic-adr are universal at team /
|
|
329
|
+
// mature (in REQUIRED_SKILLS) AND conditional at solo (in
|
|
330
|
+
// CONDITIONAL_SKILLS) per review B1 (v0.11.3). Without the Set, the
|
|
331
|
+
// managed-skills section would list those rows twice.
|
|
305
332
|
const skillDisplayOrder = [
|
|
306
|
-
...
|
|
307
|
-
|
|
333
|
+
...new Set([
|
|
334
|
+
...REQUIRED_SKILLS,
|
|
335
|
+
...CONDITIONAL_SKILLS.map((s) => s.name),
|
|
336
|
+
]),
|
|
308
337
|
].filter((s) => installedSkillSet.has(s));
|
|
309
338
|
|
|
310
339
|
const confirmAppend = interactive
|
package/src/commands/profile.js
CHANGED
|
@@ -17,6 +17,10 @@ const AGENT_LABEL = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
function readProjectProfile(cwd) {
|
|
20
|
+
// Returns { agent: profileName }. Use `loadProjectStates(cwd)` instead
|
|
21
|
+
// when you need both the profile and the full state objects in a single
|
|
22
|
+
// pass (avoids the TOCTOU window where state files could be deleted
|
|
23
|
+
// between two reads — review C2, v0.11.3).
|
|
20
24
|
const perAgent = {};
|
|
21
25
|
for (const agent of VALID_AGENTS) {
|
|
22
26
|
const state = loadState(cwd, agent);
|
|
@@ -25,6 +29,23 @@ function readProjectProfile(cwd) {
|
|
|
25
29
|
return perAgent;
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
function loadProjectStates(cwd) {
|
|
33
|
+
// Single-pass load of every per-agent state file. Returns
|
|
34
|
+
// { statesByAgent, profilesByAgent }. Both objects share the same agent
|
|
35
|
+
// keys so callers can iterate one and look up the other without a
|
|
36
|
+
// second filesystem read.
|
|
37
|
+
const statesByAgent = {};
|
|
38
|
+
const profilesByAgent = {};
|
|
39
|
+
for (const agent of VALID_AGENTS) {
|
|
40
|
+
const state = loadState(cwd, agent);
|
|
41
|
+
if (state) {
|
|
42
|
+
statesByAgent[agent] = state;
|
|
43
|
+
profilesByAgent[agent] = state.profile ?? DEFAULT_PROFILE;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { statesByAgent, profilesByAgent };
|
|
47
|
+
}
|
|
48
|
+
|
|
28
49
|
function showProfile(cwd) {
|
|
29
50
|
const perAgent = readProjectProfile(cwd);
|
|
30
51
|
if (Object.keys(perAgent).length === 0) {
|
|
@@ -64,8 +85,12 @@ function formatRule(rule) {
|
|
|
64
85
|
async function setProfile(cwd, name, opts) {
|
|
65
86
|
validateProfile(name);
|
|
66
87
|
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
// Single load — reuse below to write. Avoids the TOCTOU window where
|
|
89
|
+
// state files could be deleted between read and re-read (review C2,
|
|
90
|
+
// v0.11.3). Previous implementation called readProjectProfile then
|
|
91
|
+
// loadState again per agent in the write loop.
|
|
92
|
+
const { statesByAgent, profilesByAgent } = loadProjectStates(cwd);
|
|
93
|
+
if (Object.keys(statesByAgent).length === 0) {
|
|
69
94
|
throw new Error(
|
|
70
95
|
'no agentic install detected. Run `agentic init --profile <name>` first.'
|
|
71
96
|
);
|
|
@@ -73,7 +98,7 @@ async function setProfile(cwd, name, opts) {
|
|
|
73
98
|
|
|
74
99
|
const interactive = process.stdout.isTTY && !opts.yes;
|
|
75
100
|
|
|
76
|
-
const currentProfiles = [...new Set(Object.values(
|
|
101
|
+
const currentProfiles = [...new Set(Object.values(profilesByAgent))];
|
|
77
102
|
if (currentProfiles.length === 1 && currentProfiles[0] === name) {
|
|
78
103
|
process.stdout.write(`Profile already \`${name}\` for all installed agents. No change.\n`);
|
|
79
104
|
return;
|
|
@@ -82,7 +107,7 @@ async function setProfile(cwd, name, opts) {
|
|
|
82
107
|
if (interactive) {
|
|
83
108
|
p.intro(`agentic profile set ${name}`);
|
|
84
109
|
p.note(
|
|
85
|
-
Object.entries(
|
|
110
|
+
Object.entries(profilesByAgent)
|
|
86
111
|
.map(([agent, profile]) => `${AGENT_LABEL[agent]}: ${profile} → ${name}`)
|
|
87
112
|
.join('\n'),
|
|
88
113
|
'Profile change'
|
|
@@ -101,9 +126,9 @@ async function setProfile(cwd, name, opts) {
|
|
|
101
126
|
}
|
|
102
127
|
|
|
103
128
|
// Write the new profile to each installed agent's state file before
|
|
104
|
-
// running update, so update reads the new profile.
|
|
105
|
-
|
|
106
|
-
|
|
129
|
+
// running update, so update reads the new profile. Reuses the in-memory
|
|
130
|
+
// states loaded above — no second filesystem read.
|
|
131
|
+
for (const [agent, state] of Object.entries(statesByAgent)) {
|
|
107
132
|
state.profile = name;
|
|
108
133
|
saveState(cwd, agent, state);
|
|
109
134
|
}
|
package/src/commands/update.js
CHANGED
|
@@ -128,18 +128,27 @@ function previouslyOptedConditional(previousStates, currentAgents, profileName)
|
|
|
128
128
|
return [...opted];
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
function profileFromStates(
|
|
132
|
-
//
|
|
133
|
-
//
|
|
131
|
+
function profileFromStates(statesByAgent, currentAgents) {
|
|
132
|
+
// Profile must match across every installed agent in the project — not
|
|
133
|
+
// only across the agents the current invocation targets. Without this,
|
|
134
|
+
// `--agent claude-code` on a project where codex was installed with a
|
|
135
|
+
// different profile masks the disagreement and produces inconsistent
|
|
136
|
+
// installs. Per review B2 (v0.11.3): always inspect the FULL set of
|
|
137
|
+
// loaded states, not the narrowed slice.
|
|
134
138
|
const seen = new Set();
|
|
135
|
-
for (const agent of
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
for (const [agent, state] of Object.entries(statesByAgent)) {
|
|
140
|
+
if (state?.profile) seen.add(state.profile);
|
|
141
|
+
}
|
|
142
|
+
if (seen.size === 0) {
|
|
143
|
+
// No state on disk for any agent. Fall back to the default; current
|
|
144
|
+
// invocation is a fresh / legacy install handled by the legacy path.
|
|
145
|
+
return DEFAULT_PROFILE;
|
|
138
146
|
}
|
|
139
|
-
if (seen.size === 0) return DEFAULT_PROFILE;
|
|
140
147
|
if (seen.size > 1) {
|
|
141
148
|
throw new Error(
|
|
142
|
-
`state files disagree on profile (${[...seen].join(
|
|
149
|
+
`state files disagree on profile (${[...seen].join(
|
|
150
|
+
', '
|
|
151
|
+
)}). Run \`agentic profile set <name>\` to reconcile across all installed agents before re-running update.`
|
|
143
152
|
);
|
|
144
153
|
}
|
|
145
154
|
return [...seen][0];
|
|
@@ -172,7 +181,10 @@ export async function updateCommand(opts) {
|
|
|
172
181
|
previousStates[agent] = statesByAgent[agent] ?? null;
|
|
173
182
|
}
|
|
174
183
|
|
|
175
|
-
|
|
184
|
+
// Pass the FULL loaded set, not the narrowed slice. profileFromStates
|
|
185
|
+
// surfaces cross-agent disagreement even when the current invocation
|
|
186
|
+
// targets only one agent (review B2, v0.11.3).
|
|
187
|
+
const profileName = profileFromStates(statesByAgent, agents);
|
|
176
188
|
const previousOpted = previouslyOptedConditional(
|
|
177
189
|
previousStates,
|
|
178
190
|
agents,
|
|
@@ -269,13 +281,14 @@ export async function updateCommand(opts) {
|
|
|
269
281
|
confirmReplace,
|
|
270
282
|
previousStates: { [agent]: previousStates[agent] ?? null },
|
|
271
283
|
kitVersion: pkg.version,
|
|
284
|
+
profile: profileName,
|
|
272
285
|
dryRun,
|
|
273
286
|
force,
|
|
274
287
|
});
|
|
275
288
|
allActions.push(...result.actions);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
nextStates[agent] =
|
|
289
|
+
// installSkills now stamps `profile` into nextStates per review C3.
|
|
290
|
+
// No post-hoc injection.
|
|
291
|
+
nextStates[agent] = result.nextStates[agent];
|
|
279
292
|
}
|
|
280
293
|
|
|
281
294
|
if (!dryRun) {
|
|
@@ -284,9 +297,15 @@ export async function updateCommand(opts) {
|
|
|
284
297
|
}
|
|
285
298
|
}
|
|
286
299
|
|
|
300
|
+
// Dedup: agentic-architecture and agentic-adr are universal at team /
|
|
301
|
+
// mature (in REQUIRED_SKILLS) AND conditional at solo (in
|
|
302
|
+
// CONDITIONAL_SKILLS) per review B1 (v0.11.3). Without the Set, the
|
|
303
|
+
// managed-skills section would list those rows twice.
|
|
287
304
|
const skillDisplayOrder = [
|
|
288
|
-
...
|
|
289
|
-
|
|
305
|
+
...new Set([
|
|
306
|
+
...REQUIRED_SKILLS,
|
|
307
|
+
...CONDITIONAL_SKILLS.map((s) => s.name),
|
|
308
|
+
]),
|
|
290
309
|
].filter((s) => installedSkillSet.has(s));
|
|
291
310
|
|
|
292
311
|
const confirmAppend = interactive
|
package/src/index.js
CHANGED
|
@@ -36,12 +36,25 @@ export async function run(argv) {
|
|
|
36
36
|
.option('--force', 'overwrite user-edited files on conflict (non-interactive default: no)')
|
|
37
37
|
.action(updateCommand);
|
|
38
38
|
|
|
39
|
+
// Profile command accepts two positionals so `agentic profile set <name>`
|
|
40
|
+
// captures the name natively. Per review C1 (v0.11.3): the prior single-
|
|
41
|
+
// positional form had Commander swallow the second arg, leaving the
|
|
42
|
+
// documented `Usage: agentic profile set <name>` error message misleading.
|
|
43
|
+
// All forms work now:
|
|
44
|
+
// agentic profile → show
|
|
45
|
+
// agentic profile show → show
|
|
46
|
+
// agentic profile list → list
|
|
47
|
+
// agentic profile set <name> → set
|
|
48
|
+
// agentic profile <name> → shorthand for `set <name>`
|
|
49
|
+
// agentic profile set --name <name> → flag form (back-compat)
|
|
39
50
|
program
|
|
40
|
-
.command('profile [subcommand]')
|
|
51
|
+
.command('profile [subcommand] [name]')
|
|
41
52
|
.description('Show, list, or set the project maturity profile (poc | solo | team | mature)')
|
|
42
|
-
.option('-n, --name <name>', 'profile name
|
|
53
|
+
.option('-n, --name <name>', 'profile name (alternative to positional, for `set` subcommand)')
|
|
43
54
|
.option('-y, --yes', 'skip confirmation prompts (non-interactive)')
|
|
44
|
-
.action(
|
|
55
|
+
.action((subcommand, name, opts) =>
|
|
56
|
+
profileCommand(subcommand, { ...opts, name: opts.name ?? name })
|
|
57
|
+
);
|
|
45
58
|
|
|
46
59
|
await program.parseAsync(argv);
|
|
47
60
|
}
|
package/src/lib/install.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import { basename, dirname, join, relative, sep as PATH_SEP } from 'node:path';
|
|
14
14
|
import { SCHEMA_VERSION } from './state.js';
|
|
15
|
+
import { DEFAULT_PROFILE, validateProfile } from './profiles.js';
|
|
15
16
|
|
|
16
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
const KIT_ROOT = join(__dirname, '..', '..');
|
|
@@ -190,9 +191,11 @@ export async function installSkills({
|
|
|
190
191
|
confirmReplace = async () => false,
|
|
191
192
|
previousStates = {},
|
|
192
193
|
kitVersion = null,
|
|
194
|
+
profile = null,
|
|
193
195
|
dryRun = false,
|
|
194
196
|
force = false,
|
|
195
197
|
}) {
|
|
198
|
+
if (profile !== null) validateProfile(profile);
|
|
196
199
|
const actions = [];
|
|
197
200
|
const nextStates = {};
|
|
198
201
|
|
|
@@ -302,10 +305,15 @@ export async function installSkills({
|
|
|
302
305
|
};
|
|
303
306
|
}
|
|
304
307
|
|
|
308
|
+
// Profile resolution order: explicit `profile` arg > prior state's
|
|
309
|
+
// profile > DEFAULT_PROFILE. installSkills is the single owner of the
|
|
310
|
+
// returned nextStates' shape; callers no longer inject `profile`
|
|
311
|
+
// post-hoc per review C3 (v0.11.3).
|
|
305
312
|
nextStates[agent] = {
|
|
306
313
|
schemaVersion: SCHEMA_VERSION,
|
|
307
314
|
kitVersion: kitVersion ?? prev?.kitVersion ?? null,
|
|
308
315
|
agent,
|
|
316
|
+
profile: profile ?? prev?.profile ?? DEFAULT_PROFILE,
|
|
309
317
|
skills: nextSkills,
|
|
310
318
|
};
|
|
311
319
|
}
|