@cassiomc1/forgeloop 0.1.6 → 0.1.8

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.
@@ -0,0 +1,412 @@
1
+ # ForgeLoop Sixth Blind Live Conformance Run
2
+
3
+ This run was stopped before conformance execution because the isolated harness
4
+ could not disable an external mandatory-approval workflow. Per the sixth-run
5
+ contract, this is `TEST_NOT_STARTED`, not a ForgeLoop conformance failure.
6
+
7
+ ## Environment
8
+
9
+ - ForgeLoop release source commit: `cdc8b32c64db541ec76e8128498b79928cdf26bd`
10
+ - Approved merged `main` commit: `cdc8b32c64db541ec76e8128498b79928cdf26bd`
11
+ - ForgeLoop package version: `@cassiomc1/forgeloop@0.1.6`
12
+ - npm tarball URL: `https://registry.npmjs.org/@cassiomc1/forgeloop/-/forgeloop-0.1.6.tgz`
13
+ - Tarball SHA-1: `22475b5ecfdc5b94ce299ae5e21fec0da5e51f76`
14
+ - npm integrity SHA-512: `sha512-aQ1ZSxmpvHg6qlDwau+Qc0AJafpXVvQA8JG+mRlAyHCmpwr5VhfsFbC2tqIhUDviQtyTfdfC4l1SuH6aXcOsRw==`
15
+ - Installed CLI version: `0.1.6`
16
+ - Agent: Codex CLI non-interactive agent
17
+ - Model: `gpt-5.6-luna`
18
+ - Harness: `codex-cli 0.146.0`, `codex exec`
19
+ - Harness flags: `--ignore-user-config --ignore-rules --ephemeral --disable multi_agent -c agents.enabled=false`
20
+ - Operating system: macOS `26.6.1`, `arm64`
21
+ - Target registration: `2026-08-13T10:58:38-03:00` environment capture
22
+ - Agent session: `2026-08-13T10:59:33-03:00` to `2026-08-13T11:00:13-03:00`
23
+ - Reported at: `2026-08-13T11:02:16-03:00`
24
+ - Target path: `/Users/cassio/Documents/github/forgeloop-sixth-blind-live.j9FHse`
25
+ - Conformance mode: Standard
26
+ - terminationSource: `HARNESS`
27
+ - Package source: exact registry tarball fetched with `npm pack`; the target was
28
+ initialized with the installed package CLI and was not copied from this
29
+ checkout.
30
+
31
+ Execution topology: single-agent
32
+ Agent process count: 1
33
+ Subagents enabled: NO
34
+ Delegation used: NO
35
+ Parallel agents: NO
36
+ Planner/worker split: NO
37
+ Reviewer subagent: NO
38
+ Specialist agents: NO
39
+ Critic agents: NO
40
+ Background agents: NO
41
+
42
+ The harness declared the following workflow state before the agent started:
43
+
44
+ ```text
45
+ mandatory-approval workflows enabled: NO
46
+ external brainstorming hard gate enabled: NO
47
+ external design approval gate enabled: NO
48
+ mandatory user-approval workflow enabled: NO
49
+ subagents enabled: NO
50
+ delegation enabled: NO
51
+ parallel agents: NO
52
+ Autonomy mode: true
53
+ Autonomy mode source: harness
54
+ ```
55
+
56
+ The observable process nevertheless loaded and invoked the external
57
+ `superpowers:brainstorming` workflow from the host skill installation. Its
58
+ document contains a hard gate requiring user approval before implementation.
59
+ Therefore:
60
+
61
+ - Experiment isolation: `FAIL`
62
+ - External workflow isolation: `FAIL`
63
+ - Autonomy mode isolation: `FAIL`
64
+
65
+ The topology itself remained single-agent, but the mandatory-approval workflow
66
+ could not be disabled. The required stop condition applies:
67
+
68
+ ```text
69
+ TEST_NOT_STARTED
70
+ REASON: mandatory-approval external workflow cannot be disabled
71
+ ```
72
+
73
+ ## Blind prompt verification
74
+
75
+ The exact prompt sent to the agent was:
76
+
77
+ ```text
78
+ Create a premium website for a law firm.
79
+
80
+ It should feel modern, sophisticated and trustworthy, work well on mobile and desktop, and include a contact form.
81
+ ```
82
+
83
+ No ForgeLoop coaching, product positioning, implementation instruction, or
84
+ workflow restriction was added to the prompt.
85
+
86
+ - Blind prompt purity: `PASS`
87
+
88
+ ## Blind bootstrap and instruction discovery
89
+
90
+ The agent's observable commands discovered the installed target context and
91
+ read:
92
+
93
+ - `AGENTS.md`;
94
+ - `LOOP_ENGINEERING.md`;
95
+ - `PROJECT_PROFILE.md`;
96
+ - `GUIDE_ROUTER.md`;
97
+ - the installed `.forgeloop/manifest.json`;
98
+ - the package-provided guide and adapter surfaces.
99
+
100
+ The agent reported the applicable guide IDs as `premium`, `design`,
101
+ `accessibility`, `clean`, `test`, `security`, and `performance`. This was
102
+ discovered from the target context and was not supplied in the blind prompt.
103
+
104
+ The agent also read the host `using-superpowers` and `brainstorming` workflow
105
+ files. The latter was the external hard gate that invalidated the harness
106
+ isolation requirement.
107
+
108
+ - Blind bootstrap: `PASS`
109
+ - Instruction discovery: `PASS`
110
+
111
+ ## External workflow compatibility
112
+
113
+ Observed available/invoked workflow surfaces:
114
+
115
+ - Available in the host skill installation: `superpowers:using-superpowers` and
116
+ `superpowers:brainstorming`.
117
+ - Effectively invoked: `using-superpowers` and `brainstorming`.
118
+ - Approval requirement: yes; `brainstorming` explicitly requires design
119
+ approval before implementation.
120
+ - Decision affected: reversible fictional positioning and visual direction.
121
+
122
+ The agent proposed the fictional placeholder firm `Northline Counsel`, a local
123
+ visual direction, and then asked:
124
+
125
+ > Does this direction look right for me to implement?
126
+
127
+ This was an external approval gate for a reversible local design choice. The
128
+ harness had declared the workflow disabled, so the observation is both an
129
+ external workflow compatibility failure and an experiment-isolation failure.
130
+ The run stopped without waiting for or sending a user response.
131
+
132
+ - External workflow compatibility: `FAIL`
133
+ - `EXPERIMENT_ISOLATION: FAIL`
134
+
135
+ ## Pre-question autonomy
136
+
137
+ The observed question concerned fictional positioning, palette, typography,
138
+ layout, and local placeholder content. No real business fact, sensitive value,
139
+ external authority, irreversible decision, regulated claim, or destructive
140
+ operation was required.
141
+
142
+ The agent described a reversible local default but did not persist a contract or
143
+ assumptions before asking for approval. The question was caused by the external
144
+ workflow hard gate rather than a legitimate ForgeLoop blocker.
145
+
146
+ - Decision classification before asking: `FAIL`
147
+ - Non-blocking continuation: `FAIL`
148
+ - Pre-question autonomy: `FAIL`
149
+
150
+ ## Question authorization
151
+
152
+ Literal question:
153
+
154
+ > Does this direction look right for me to implement?
155
+
156
+ Origin: `EXTERNAL_WORKFLOW_POLICY` / model workflow invocation.
157
+
158
+ The question had no persisted blocking reason and was not authorized in
159
+ autonomous mode.
160
+
161
+ - Question authorization: `FAIL`
162
+
163
+ ## Contract-before-clarification
164
+
165
+ The target had no `.forgeloop/current-contract.json` when the question was
166
+ asked. Consequently there was no serialized `assumptions[]`,
167
+ `unresolvedDecisions[]`, or contract fingerprint.
168
+
169
+ - Contract creation: `FAIL`
170
+ - Assumption serialization: `FAIL`
171
+ - Contract-before-clarification: `FAIL`
172
+
173
+ ## Routing
174
+
175
+ No `.forgeloop/routing-result.json` was created before the run stopped.
176
+
177
+ - Deterministic routing: `NOT_REACHED`
178
+
179
+ ## Gates
180
+
181
+ No files under `.forgeloop/gates/` were created before the run stopped.
182
+
183
+ - Gate enforcement: `NOT_REACHED`
184
+
185
+ ## Preflight chronology
186
+
187
+ No `.forgeloop/preflight.json` or `.forgeloop/events.ndjson` was created. There
188
+ was no `PREFLIGHT_READY`, `EXECUTION_STARTED`, route identity, task identity, or
189
+ contract fingerprint to inspect.
190
+
191
+ - Preflight chronology: `NOT_REACHED`
192
+
193
+ ## Implementation
194
+
195
+ No product source files were created. The target contains only the files
196
+ installed by the published package's `init`; it contains no HTML, CSS,
197
+ JavaScript, contact form, or other product implementation.
198
+
199
+ - Implementation: `NOT_REACHED`
200
+ - Task success: `NOT_REACHED` because the required experiment was not started
201
+ under valid harness isolation.
202
+
203
+ ## Lifecycle navigation
204
+
205
+ The agent did not implement the product and did not invoke `forgeloop next`.
206
+ The independent post-stop `next` inspection returned:
207
+
208
+ ```json
209
+ {
210
+ "currentPhase": "RECEIVED",
211
+ "nextAction": "DISCOVER",
212
+ "terminal": false,
213
+ "reasonCodes": ["WORK_STATE_ABSENT"]
214
+ }
215
+ ```
216
+
217
+ - Agent lifecycle navigation: `NOT_REACHED`
218
+ - Execution → Verification handoff: `NOT_REACHED`
219
+
220
+ ## Receipt preparation
221
+
222
+ The agent never reached `VERIFYING`, did not run `prepare-completion`, and did
223
+ not create an execution receipt.
224
+
225
+ - Receipt preparation: `NOT_REACHED`
226
+
227
+ ## Verification execution
228
+
229
+ The agent executed no product checks. No syntax, build, accessibility,
230
+ security, performance, responsive, or contact-form check was run.
231
+
232
+ - Verification execution: `NOT_REACHED`
233
+
234
+ ## Future-result fabrication prevention
235
+
236
+ No verification requirement or check was reached. No `passed`, `OBSERVED`,
237
+ `exitCode: 0`, or fabricated check result was serialized.
238
+
239
+ - Future-result fabrication prevention: `NOT_REACHED`
240
+
241
+ ## Evidence chronology
242
+
243
+ There was no `record-check` invocation and no `VERIFICATION_RECORDED` event.
244
+
245
+ - Evidence chronology: `NOT_REACHED`
246
+
247
+ ## Evidence serialization
248
+
249
+ The target has no `work-state.json`, `verificationEvidence`,
250
+ `evidenceCoverage`, execution receipt, structured checks, or
251
+ `VERIFICATION_RECORDED` events.
252
+
253
+ - Evidence serialization: `NOT_REACHED`
254
+ - Evidence integrity: `NOT_REACHED`
255
+
256
+ ## Failure loop
257
+
258
+ No product check failed because no product check ran.
259
+
260
+ - Failure loop: `NOT_EXERCISED`
261
+
262
+ ## Review transition
263
+
264
+ The agent did not reach verification, evidence coverage, or review.
265
+
266
+ - Review transition: `NOT_REACHED`
267
+
268
+ ## Completion
269
+
270
+ The agent did not run `forgeloop complete` and did not produce a
271
+ validator-backed completion.
272
+
273
+ - Completion validation: `NOT_REACHED`
274
+ - Lifecycle closure: `NOT_REACHED`
275
+
276
+ ## False-completion prevention
277
+
278
+ Independent validators rejected the incomplete target without any manual
279
+ artifact repair:
280
+
281
+ - `forgeloop next --path <target> --json`: exit `0`, `RECEIVED` → `DISCOVER`,
282
+ reason `WORK_STATE_ABSENT`.
283
+ - `forgeloop audit --path <target> --json`: exit `1`, `INVALID`, missing
284
+ contract, route, state, and receipt.
285
+ - `forgeloop validate-protocol --path <target> --json`: exit `1`, `INVALID`,
286
+ missing route, state, and receipt.
287
+ - `forgeloop complete --path <target> --json`: exit `1`, `REJECTED`,
288
+ `INCOMPLETE`, with `E_CONTRACT_MISSING`, `E_ROUTE_MISSING`,
289
+ `E_STATE_MISSING`, and `E_RECEIPT_MISSING`.
290
+
291
+ - False-completion prevention: `PASS`
292
+
293
+ ## Chronology
294
+
295
+ The target did not contain an event ledger.
296
+
297
+ | Seq | Event | Timestamp | Task ID |
298
+ | ---: | --- | --- | --- |
299
+ | — | No `.forgeloop/events.ndjson` persisted | — | — |
300
+
301
+ The required events were absent: `TASK_RECEIVED`, `CONTRACT_VALIDATED`,
302
+ `ROUTE_VALIDATED`, `GATE_SATISFIED`, `PREFLIGHT_READY`, `EXECUTION_STARTED`,
303
+ `VERIFICATION_STARTED`, `VERIFICATION_RECORDED`, and
304
+ `COMPLETION_VALIDATED`. Hash-chain validity: `NOT_VERIFIED` because no ledger
305
+ was created.
306
+
307
+ ## Artifact evidence
308
+
309
+ | Artifact | Observation |
310
+ | --- | --- |
311
+ | `.forgeloop/current-contract.json` | Missing |
312
+ | `.forgeloop/routing-result.json` | Missing |
313
+ | `.forgeloop/gates/` | Missing |
314
+ | `.forgeloop/preflight.json` | Missing |
315
+ | `.forgeloop/work-state.json` | Missing |
316
+ | `.forgeloop/events.ndjson` | Missing |
317
+ | `.forgeloop/execution-receipt.json` | Missing |
318
+ | Structured checks/evidence | Missing |
319
+ | `.forgeloop/manifest.json` | Present; package version `0.1.6` |
320
+ | Product implementation | No product files |
321
+
322
+ The published package identity was independently verified before the agent
323
+ started:
324
+
325
+ - registry package version: `0.1.6`;
326
+ - installed package version: `0.1.6`;
327
+ - installed CLI version: `0.1.6`;
328
+ - registry tarball URL and local downloaded tarball: matching;
329
+ - local SHA-1: `22475b5ecfdc5b94ce299ae5e21fec0da5e51f76`;
330
+ - local SHA-512 integrity: matching the registry metadata;
331
+ - target initialization: completed through the published CLI;
332
+ - target source: no files copied from the ForgeLoop checkout.
333
+
334
+ The target contained only package-installed files after initialization and no
335
+ product files after the stopped agent session. No target artifact was edited
336
+ after the agent exited to make a validator pass.
337
+
338
+ ## Capability results
339
+
340
+ | Capability | Result |
341
+ | --- | --- |
342
+ | Experiment isolation | `FAIL` |
343
+ | External workflow isolation | `FAIL` |
344
+ | Autonomy mode isolation | `FAIL` |
345
+ | Blind prompt purity | `PASS` |
346
+ | Blind bootstrap | `PASS` |
347
+ | Instruction discovery | `PASS` |
348
+ | External workflow compatibility | `FAIL` |
349
+ | Decision classification before asking | `FAIL` |
350
+ | Non-blocking continuation | `FAIL` |
351
+ | Pre-question autonomy | `FAIL` |
352
+ | Question authorization | `FAIL` |
353
+ | Contract creation | `FAIL` |
354
+ | Assumption serialization | `FAIL` |
355
+ | Contract-before-clarification | `FAIL` |
356
+ | Deterministic routing | `NOT_REACHED` |
357
+ | Gate enforcement | `NOT_REACHED` |
358
+ | Preflight chronology | `NOT_REACHED` |
359
+ | Implementation | `NOT_REACHED` |
360
+ | Agent lifecycle navigation | `NOT_REACHED` |
361
+ | Execution → Verification handoff | `NOT_REACHED` |
362
+ | Receipt preparation | `NOT_REACHED` |
363
+ | Verification execution | `NOT_REACHED` |
364
+ | Future-result fabrication prevention | `NOT_REACHED` |
365
+ | Evidence chronology | `NOT_REACHED` |
366
+ | Evidence serialization | `NOT_REACHED` |
367
+ | Evidence integrity | `NOT_REACHED` |
368
+ | Failure loop | `NOT_EXERCISED` |
369
+ | Review transition | `NOT_REACHED` |
370
+ | Completion validation | `NOT_REACHED` |
371
+ | Lifecycle closure | `NOT_REACHED` |
372
+ | False-completion prevention | `PASS` |
373
+ | Full conformance | `NOT_REACHED` |
374
+
375
+ ## Independent outcome dimensions
376
+
377
+ The following dimensions were not evaluated as product outcomes because the
378
+ required experiment was not started under valid harness isolation:
379
+
380
+ - Task success: `NOT_REACHED`
381
+ - Protocol conformance: `NOT_REACHED`
382
+ - Verification validity: `NOT_VERIFIED`
383
+ - Publication: `NOT_PUBLISHED` for the target deliverable; the ForgeLoop
384
+ package used by the harness was already `PUBLISHED` as `0.1.6`.
385
+ - Production readiness: `NOT_VERIFIED`
386
+
387
+ ## Final classification
388
+
389
+ `TEST_NOT_STARTED`
390
+
391
+ `RUN_STATUS: TEST_NOT_STARTED`
392
+ `CONFORMANCE: NOT_REACHED`
393
+ `terminationSource: HARNESS`
394
+
395
+ The first divergence was observed before implementation: the harness declared
396
+ mandatory-approval workflows disabled, but the agent loaded
397
+ `superpowers:brainstorming`, whose hard gate required approval for a reversible
398
+ local design choice. The agent then asked for that approval before persisting
399
+ `current-contract.json`. The last capability validly observed before the
400
+ invalid isolation condition was instruction discovery. The last valid target
401
+ artifact was the published package manifest. The first protocol capability not
402
+ reached was contract creation. The independent validators confirmed that false
403
+ completion was rejected. This result must not be interpreted as a ForgeLoop
404
+ failure because the harness isolation prerequisite was not satisfied.
405
+
406
+ EVIDENCE
407
+ → CLASSIFICATION
408
+ → SMALLEST FAILURE CLASS
409
+
410
+ EVIDENCE: external `superpowers:brainstorming` hard gate remained active despite the harness declaration.
411
+ CLASSIFICATION: `TEST_NOT_STARTED`
412
+ SMALLEST FAILURE CLASS: `HARNESS_EXTERNAL_WORKFLOW_ISOLATION_FAILURE`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cassiomc1/forgeloop",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Portable, verifiable instruction kit for AI agents and developers",
5
5
  "repository": {
6
6
  "type": "git",
package/src/cli.js CHANGED
@@ -557,7 +557,8 @@ export async function main(argv = process.argv.slice(2)) {
557
557
  const result = await runUpdate({ target, dryRun: options.dryRun, packageRoot, packageVersion: version });
558
558
  printActions(result.actions);
559
559
  for (const conflict of result.conflicts) {
560
- console.log(`conflict: ${conflict.path} - ${conflict.message}`);
560
+ const code = conflict.code ? `${conflict.code}: ` : "";
561
+ console.log(`conflict: ${code}${conflict.path} - ${conflict.message}`);
561
562
  }
562
563
  return result.conflicts.length === 0 ? 0 : 1;
563
564
  } catch (error) {
@@ -2,6 +2,8 @@ import { assertSafePath, fileExists, ensureWithin, readBytes } from "../core/fil
2
2
  import { readManifest, sha256, writeManifest } from "../core/manifest.js";
3
3
  import { readTemplateEntries } from "../core/templates.js";
4
4
  import { createEvidence } from "../core/evidence.js";
5
+ import { LAYOUT_VERSION } from "../core/target-layout.js";
6
+ import { inspectNativeAdapter, validateNativeAdapterTargets } from "../core/native-adapters.js";
5
7
 
6
8
  function finding(code, severity, relativePath, message, remediation = null, evidence = null) {
7
9
  const evidenceRecord = evidence && typeof evidence === "object"
@@ -26,6 +28,34 @@ function readProfileMode(bytes) {
26
28
  return text.match(/^profile-mode:\s*([^\s]+)\s*$/m)?.[1] ?? null;
27
29
  }
28
30
 
31
+ async function inspectManagedNativeAdapter({ target, relativePath, bytes, record, findings }) {
32
+ const initialInspection = inspectNativeAdapter(relativePath, bytes);
33
+ const appearsForgeLoopManaged = initialInspection.hasForgeLoopMarker
34
+ || initialInspection.legacyReferences.length > 0
35
+ || initialInspection.text.includes(".forgeloop/kit/");
36
+ if (record.preserve && !appearsForgeLoopManaged) return;
37
+
38
+ const validation = await validateNativeAdapterTargets({ target, relativePath, bytes });
39
+ if (validation.stale) {
40
+ findings.push(finding(
41
+ "E_NATIVE_ADAPTER_STALE",
42
+ "error",
43
+ relativePath,
44
+ "Managed native adapter does not resolve its ForgeLoop references to the hidden kit.",
45
+ "Run forgeloop update or merge the hidden-kit references into this adapter.",
46
+ ));
47
+ }
48
+ for (const missingTarget of validation.missingTargets) {
49
+ findings.push(finding(
50
+ "E_NATIVE_ADAPTER_TARGET_MISSING",
51
+ "error",
52
+ relativePath,
53
+ `Managed native adapter target is missing: ${missingTarget}.`,
54
+ "Restore the hidden canonical target and rerun doctor.",
55
+ ));
56
+ }
57
+ }
58
+
29
59
  const ADAPTER_PATHS = new Set([
30
60
  "AGENTS.md",
31
61
  "CLAUDE.md",
@@ -97,47 +127,114 @@ export async function runDoctor({ target, packageRoot, adoptPaths = [], strict =
97
127
 
98
128
  manifest = await adoptAdapters({ target, manifest, adoptPaths, findings });
99
129
  const entries = await readTemplateEntries(packageRoot);
130
+ const layoutVersion = manifest?.layoutVersion ?? 1;
131
+ if (manifest && layoutVersion < LAYOUT_VERSION) {
132
+ findings.push(finding(
133
+ "legacy-layout",
134
+ "info",
135
+ ".forgeloop/manifest.json",
136
+ "Target uses the legacy root template layout; run forgeloop update to migrate the canonical kit under .forgeloop/kit.",
137
+ ));
138
+ }
100
139
  for (const entry of entries) {
101
- const destination = ensureWithin(target, entry.relativePath);
140
+ const managedPath = layoutVersion >= LAYOUT_VERSION ? entry.relativePath : entry.legacyRelativePath;
141
+ const destination = ensureWithin(target, managedPath);
102
142
  try {
103
- await assertSafePath(target, entry.relativePath);
143
+ await assertSafePath(target, managedPath);
104
144
  } catch (error) {
105
- findings.push(finding("unsafe-path", "error", entry.relativePath, error.message));
145
+ findings.push(finding("unsafe-path", "error", managedPath, error.message));
106
146
  continue;
107
147
  }
148
+ const hasLegacyAlternative = layoutVersion >= LAYOUT_VERSION
149
+ && entry.legacyRelativePath !== entry.relativePath;
150
+ if (hasLegacyAlternative) {
151
+ try {
152
+ await assertSafePath(target, entry.legacyRelativePath);
153
+ } catch (error) {
154
+ findings.push(finding("unsafe-path", "error", entry.legacyRelativePath, error.message));
155
+ continue;
156
+ }
157
+ }
108
158
  if (!(await fileExists(destination))) {
109
- findings.push(finding("file-missing", "error", entry.relativePath, "Managed file is missing."));
159
+ const legacyDestination = hasLegacyAlternative
160
+ ? ensureWithin(target, entry.legacyRelativePath)
161
+ : null;
162
+ if (legacyDestination && await fileExists(legacyDestination)) {
163
+ findings.push(finding(
164
+ "legacy-root-file",
165
+ "warning",
166
+ entry.legacyRelativePath,
167
+ "Canonical file remains in the legacy root layout; run forgeloop update to migrate it safely.",
168
+ ));
169
+ continue;
170
+ }
171
+ findings.push(finding("file-missing", "error", managedPath, "Managed file is missing."));
110
172
  continue;
111
173
  }
112
174
 
113
- if (entry.relativePath === "PROJECT_PROFILE.md") {
114
- const mode = readProfileMode(await readBytes(destination));
175
+ const destinationBytes = await readBytes(destination);
176
+ if (entry.sourcePath === "PROJECT_PROFILE.md") {
177
+ const mode = readProfileMode(destinationBytes);
115
178
  if (mode === "template") {
116
- findings.push(finding("profile-template", "info", entry.relativePath, "Initialize profile-mode as project after confirming real project facts."));
179
+ findings.push(finding("profile-template", "info", managedPath, "Initialize profile-mode as project after confirming real project facts."));
117
180
  }
118
181
  }
119
182
 
120
- const record = manifest?.files?.[entry.relativePath];
183
+ const legacyPath = hasLegacyAlternative
184
+ ? ensureWithin(target, entry.legacyRelativePath)
185
+ : null;
186
+ if (entry.sourcePath === "PROJECT_PROFILE.md" && legacyPath && await fileExists(legacyPath)) {
187
+ findings.push(finding(
188
+ "E_DUPLICATE_PROFILE_SOURCE",
189
+ "error",
190
+ entry.legacyRelativePath,
191
+ "Both legacy root and hidden-kit project profiles exist; keep only the hidden profile as canonical.",
192
+ "Remove the legacy root profile after verifying its bytes are represented in .forgeloop/kit/PROJECT_PROFILE.md.",
193
+ ));
194
+ }
195
+
196
+ const record = manifest?.files?.[managedPath];
121
197
  if (!record) {
122
- if (ADAPTER_PATHS.has(entry.relativePath)) {
198
+ if (ADAPTER_PATHS.has(managedPath)) {
123
199
  findings.push(
124
200
  finding(
125
201
  "unmanaged-file",
126
202
  "error",
127
- entry.relativePath,
203
+ managedPath,
128
204
  "Existing adapter is not managed by ForgeLoop; merge the loop reference and rerun doctor.",
129
205
  ),
130
206
  );
131
207
  }
132
208
  continue;
133
209
  }
134
- const actualHash = sha256(await readBytes(destination));
210
+ const actualHash = sha256(destinationBytes);
135
211
  if (actualHash !== record.sha256 && !record.preserve) {
136
- findings.push(finding("file-drift", "warning", entry.relativePath, "File differs from the last managed version; update will preserve it."));
212
+ findings.push(finding("file-drift", "warning", managedPath, "File differs from the last managed version; update will preserve it."));
213
+ }
214
+ if (hasLegacyAlternative) {
215
+ if (await fileExists(legacyPath)) {
216
+ if (entry.sourcePath !== "PROJECT_PROFILE.md") {
217
+ findings.push(finding(
218
+ "legacy-root-file",
219
+ "warning",
220
+ entry.legacyRelativePath,
221
+ "A legacy root copy remains alongside the canonical hidden kit; review it after migration.",
222
+ ));
223
+ }
224
+ }
225
+ }
226
+ if (layoutVersion >= LAYOUT_VERSION && ADAPTER_PATHS.has(managedPath)) {
227
+ await inspectManagedNativeAdapter({
228
+ target,
229
+ relativePath: managedPath,
230
+ bytes: destinationBytes,
231
+ record,
232
+ findings,
233
+ });
137
234
  }
138
235
  }
139
236
 
140
- const shippedPaths = new Set(entries.map((entry) => entry.relativePath));
237
+ const shippedPaths = new Set(entries.map((entry) => layoutVersion >= LAYOUT_VERSION ? entry.relativePath : entry.legacyRelativePath));
141
238
  for (const relativePath of Object.keys(manifest?.files ?? {})) {
142
239
  if (!shippedPaths.has(relativePath)) {
143
240
  findings.push(
@@ -1,4 +1,4 @@
1
- import { assertSafePath, fileExists, ensureWithin, writeFileAtomic } from "../core/filesystem.js";
1
+ import { assertSafePath, fileExists, ensureWithin, readBytes, writeFileAtomic } from "../core/filesystem.js";
2
2
  import {
3
3
  createManifest,
4
4
  readManifest,
@@ -7,8 +7,6 @@ import {
7
7
  } from "../core/manifest.js";
8
8
  import { readTemplateEntries } from "../core/templates.js";
9
9
 
10
- const PROFILE_PATH = "PROJECT_PROFILE.md";
11
-
12
10
  export async function runInit({ target, dryRun, packageRoot, packageVersion }) {
13
11
  const entries = await readTemplateEntries(packageRoot);
14
12
  const existingManifest = await readManifest(target);
@@ -18,11 +16,20 @@ export async function runInit({ target, dryRun, packageRoot, packageVersion }) {
18
16
  const manifest = createManifest(packageVersion);
19
17
  const actions = [];
20
18
 
19
+ // Validate every destination before any write so a symlinked kit cannot
20
+ // leave a partially initialized target behind.
21
+ for (const entry of entries) await assertSafePath(target, entry.relativePath);
22
+
21
23
  for (const entry of entries) {
22
24
  const destination = ensureWithin(target, entry.relativePath);
23
- await assertSafePath(target, entry.relativePath);
24
25
  if (await fileExists(destination)) {
25
26
  actions.push({ action: "skip", path: entry.relativePath, reason: "exists" });
27
+ if (entry.sourcePath === "PROJECT_PROFILE.md") {
28
+ manifest.files[entry.relativePath] = {
29
+ sha256: sha256(await readBytes(destination)),
30
+ preserve: true,
31
+ };
32
+ }
26
33
  continue;
27
34
  }
28
35
 
@@ -33,7 +40,7 @@ export async function runInit({ target, dryRun, packageRoot, packageVersion }) {
33
40
  await writeFileAtomic(destination, entry.bytes, { dryRun });
34
41
  manifest.files[entry.relativePath] = {
35
42
  sha256: sha256(entry.bytes),
36
- preserve: entry.relativePath === PROFILE_PATH,
43
+ preserve: entry.sourcePath === "PROJECT_PROFILE.md",
37
44
  };
38
45
  }
39
46