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.
@@ -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
- const { exitCode, output } = await runCli(argv, {
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.0",
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.0",
37
+ "plugin_version": "1.4.2",
38
38
  "pins_researched": "2026-08-02"
39
39
  }
40
40
  }
@@ -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.0",
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
- export const TRUST_HANDOFF_TEXT = (dir, { isGitRepo = true } = {}) => `
39
- The workspace has been written. The permission floor above is a declaration, not a grant: the
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
- const print = (s) => lines.push(s);
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
- // Drift is classified HERE — before the write phase and before the dry-run return — not after
159
- // applyPlan where the advisory report used to compute it. Two things depend on the move: the
160
- // reconcile must know what it would add in order to decide whether to write at all, and
161
- // --dry-run must be able to report those rules, which it never could before because it returned
162
- // above the only classifyDrift call in the file.
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 above, so the tracked file
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, { isGitRepo }));
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) {