create-agentic-workspace 0.4.0 → 0.4.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/bin/create-agentic-workspace.mjs +3 -2
- package/package.json +2 -2
- package/permission-floor.json +1 -1
- package/src/preview.mjs +14 -3
- package/src/run.mjs +41 -12
|
@@ -20,7 +20,9 @@ if (rawArgv.length > 0 && !rawArgv[0].startsWith('--')) {
|
|
|
20
20
|
argv = ['--dir', rawArgv[0], ...rawArgv.slice(1)];
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// runCli streams every line to `output` as it is produced (ER #95), so the returned string is a
|
|
24
|
+
// transcript for callers, not something to print again here.
|
|
25
|
+
const { exitCode } = await runCli(argv, {
|
|
24
26
|
cwd: process.cwd(),
|
|
25
27
|
isTTY: Boolean(process.stdin.isTTY),
|
|
26
28
|
input: process.stdin,
|
|
@@ -29,5 +31,4 @@ const { exitCode, output } = await runCli(argv, {
|
|
|
29
31
|
pkgDir,
|
|
30
32
|
});
|
|
31
33
|
|
|
32
|
-
process.stdout.write(`${output}\n`);
|
|
33
34
|
process.exitCode = exitCode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-agentic-workspace",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "The pre-session bootstrap wizard for an Agentic Foundry workspace: declares (never grants) the permission floor, absorbs foundry-bootstrap.sh's out-of-session identity wiring, and scaffolds a seven-file schema-valid workspace. Zero dependencies, no lifecycle scripts, no telemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"marketplace_name": "agentic-foundry",
|
|
35
35
|
"marketplace_repo": "lukasrepublic/agentic-foundry",
|
|
36
36
|
"plugin_name": "foundry",
|
|
37
|
-
"plugin_version": "1.4.
|
|
37
|
+
"plugin_version": "1.4.2",
|
|
38
38
|
"pins_researched": "2026-08-02"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/permission-floor.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"plugin_root_glob": "~/.claude/plugins/cache/*/foundry/*",
|
|
4
|
-
"generated_for_plugin_version": "1.4.
|
|
4
|
+
"generated_for_plugin_version": "1.4.2",
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
7
7
|
"rule": "Bash(~/.claude/plugins/cache/*/foundry/*/scripts/foundry-acceptance-contract-validate.py:*)",
|
package/src/preview.mjs
CHANGED
|
@@ -35,10 +35,21 @@ export function renderPreview({ plan, machineScopeWrites, map }) {
|
|
|
35
35
|
// previewed and nothing else; `git init` would be an un-previewed side effect, and the choice of
|
|
36
36
|
// remote is genuinely the reader's. `create-vite` prints its next steps the same way, for the same
|
|
37
37
|
// reason. Telling keeps the promise; doing would quietly break it.
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// `reconciledExisting` is true when --reconcile-floor actually WROTE rules into a workspace that
|
|
39
|
+
// already existed. That case inverts the sentence below, and the inversion was observed live: an
|
|
40
|
+
// operator reconciled an already-trusted workspace, restarted the session, and 42 wildcarded allow
|
|
41
|
+
// rules took effect with NO trust dialog at all. The dialog is the consent ceremony for a workspace
|
|
42
|
+
// the platform has not yet trusted; for one it already trusts, THIS CLI'S OWN preview-and-confirm is
|
|
43
|
+
// the only consent moment there will be. Saying otherwise points the operator at a review that will
|
|
44
|
+
// not happen — the one claim this tool cannot afford to get wrong, since "declares, never grants" is
|
|
45
|
+
// its whole posture. (bootstrap-cli R8, answered 2026-08-12.)
|
|
46
|
+
export const TRUST_HANDOFF_TEXT = (dir, { isGitRepo = true, reconciledExisting = false } = {}) => `
|
|
47
|
+
${reconciledExisting ? `The workspace has been written, and the permission-floor rules above were added to a workspace
|
|
48
|
+
that is ALREADY TRUSTED. On that path there is no second consent ceremony: the preview and the
|
|
49
|
+
confirmation you just gave were it, and the added \`allow\` rules are in effect from the next
|
|
50
|
+
session onward with no dialog. Re-read the list above if you have not.` : `The workspace has been written. The permission floor above is a declaration, not a grant: the
|
|
40
51
|
platform's trust dialog is the consent ceremony, and the \`allow\` rules take effect only after
|
|
41
|
-
you accept it — the dialog lists them
|
|
52
|
+
you accept it — the dialog lists them.`}
|
|
42
53
|
|
|
43
54
|
cd ${dir}${isGitRepo ? '' : `
|
|
44
55
|
git init && git add -A && git commit -m 'workspace seed'
|
package/src/run.mjs
CHANGED
|
@@ -93,7 +93,20 @@ function expandPluginRootGlob(glob, homeDir) {
|
|
|
93
93
|
* caught and turned into a refusal-shaped exit 1 (or, for a bug, exit 1 with the error message). */
|
|
94
94
|
export async function runCli(argv, { cwd, isTTY, input, output, homeDir, pkgDir }) {
|
|
95
95
|
const lines = [];
|
|
96
|
-
|
|
96
|
+
// ER #95 — STREAM, don't accumulate. This used to only push into `lines`, which bin printed
|
|
97
|
+
// after runCli returned, so every prompt fired against a blank screen: "Write the workspace as
|
|
98
|
+
// previewed above? [y/N]" was asked with nothing above it, and the preview — the file plan, the
|
|
99
|
+
// machine-scope writes OUTSIDE the target root, and the 62 permission rules — scrolled past
|
|
100
|
+
// afterwards. A confirmation whose subject is not yet on screen cannot be informed consent, and
|
|
101
|
+
// its mere presence claims a review that did not happen.
|
|
102
|
+
//
|
|
103
|
+
// It became load-bearing with --reconcile-floor. R8 was answered live: reconciling an
|
|
104
|
+
// already-trusted workspace fires NO trust dialog, so this prompt is the ONLY consent moment
|
|
105
|
+
// there is. `lines` is still accumulated because runCli's return contract exposes it.
|
|
106
|
+
const print = (s) => {
|
|
107
|
+
lines.push(s);
|
|
108
|
+
output.write(`${s}\n`);
|
|
109
|
+
};
|
|
97
110
|
|
|
98
111
|
try {
|
|
99
112
|
let parsed;
|
|
@@ -155,19 +168,15 @@ export async function runCli(argv, { cwd, isTTY, input, output, homeDir, pkgDir
|
|
|
155
168
|
|
|
156
169
|
print(renderPreview({ plan, machineScopeWrites, map }));
|
|
157
170
|
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
const { effective, unreadable } = readEffectiveRules(physicalRoot);
|
|
171
|
+
// Resolved HERE — before the write phase and before the dry-run return — because the reconcile
|
|
172
|
+
// below must know what it would add in order to decide whether to write at all, and --dry-run
|
|
173
|
+
// must be able to report those rules. That requirement is carried by the TRACKED classification
|
|
174
|
+
// a few lines down; the union classification the advisory report needs is derived after the
|
|
175
|
+
// write instead, for the reason stated there.
|
|
164
176
|
const pluginRootExpansion = expandPluginRootGlob(map.plugin_root_glob, homeDir);
|
|
165
|
-
const findings = classifyDrift(map, effective, {
|
|
166
|
-
pluginRootExpansion, unreadableOrigins: unreadable, home: homeDir,
|
|
167
|
-
});
|
|
168
177
|
|
|
169
178
|
// The reconcile classifies against the TRACKED settings.json alone. A floor rule carried only
|
|
170
|
-
// in the untracked settings.local.json reads as covered in the union
|
|
179
|
+
// in the untracked settings.local.json reads as covered in the union report below, so the tracked file
|
|
171
180
|
// would stay incomplete while the report said converged — and the repo would then ship to every
|
|
172
181
|
// other clone and to CI without it.
|
|
173
182
|
let floorPlan = null;
|
|
@@ -243,6 +252,21 @@ export async function runCli(argv, { cwd, isTTY, input, output, homeDir, pkgDir
|
|
|
243
252
|
wireIdentity({ slug, name: identity.name, email: identity.email, targetRoot: physicalRoot, homeDir });
|
|
244
253
|
}
|
|
245
254
|
|
|
255
|
+
// The advisory report describes the state the operator is LEFT in, so it is classified from the
|
|
256
|
+
// files as they now stand — AFTER applyPlan and after any reconcile write. Classifying it up
|
|
257
|
+
// front instead makes it name rules that exist by the time it prints: on the reconcile path
|
|
258
|
+
// that is every rule just added, so the run reported `added allow=42, ask=16` and then listed
|
|
259
|
+
// all 58 as absent, which reads as the write having silently failed. The create path had the
|
|
260
|
+
// same defect whenever it wrote settings.json itself. Deriving it here cannot contradict the
|
|
261
|
+
// write above it, at the cost of re-reading two small files on the write path.
|
|
262
|
+
//
|
|
263
|
+
// This is only the UNION (settings.json + settings.local.json) report. The reconcile's own
|
|
264
|
+
// classification stays above the write and over the tracked file alone — consent has to be
|
|
265
|
+
// informed by what WILL be written, which is a different question from what remains after.
|
|
266
|
+
const { effective, unreadable } = readEffectiveRules(physicalRoot);
|
|
267
|
+
const findings = classifyDrift(map, effective, {
|
|
268
|
+
pluginRootExpansion, unreadableOrigins: unreadable, home: homeDir,
|
|
269
|
+
});
|
|
246
270
|
if (findings.length > 0) {
|
|
247
271
|
print('');
|
|
248
272
|
print(`Permission-floor report (advisory, ${findings.length} finding(s)):`);
|
|
@@ -254,7 +278,12 @@ export async function runCli(argv, { cwd, isTTY, input, output, homeDir, pkgDir
|
|
|
254
278
|
// on the path is the check that covers both — a directory-only test would tell a worktree user
|
|
255
279
|
// to re-init a repository they already have.
|
|
256
280
|
const isGitRepo = fs.existsSync(path.join(targetRoot, '.git'));
|
|
257
|
-
print(TRUST_HANDOFF_TEXT(targetRoot, {
|
|
281
|
+
print(TRUST_HANDOFF_TEXT(targetRoot, {
|
|
282
|
+
isGitRepo,
|
|
283
|
+
// only when a reconcile actually wrote — a dry run, a no-op second run, or a plain scaffold
|
|
284
|
+
// all keep the standard hand-off
|
|
285
|
+
reconciledExisting: Boolean(floorPlan && floorPlan.total > 0),
|
|
286
|
+
}));
|
|
258
287
|
|
|
259
288
|
return { exitCode: exitCodeForPlan(plan), output: lines.join('\n') };
|
|
260
289
|
} catch (e) {
|