@cassiomc1/forgeloop 1.14.0 → 2.0.0-rc.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/CONTRIBUTING.md +11 -7
- package/DOCS_INDEX.md +2 -1
- package/ENG/c-development-eng.md +2 -2
- package/ENG/cpp-development-eng.md +2 -2
- package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
- package/ENG/flutter-development-eng.md +4 -2
- package/ENG/go-development-eng.md +2 -2
- package/ENG/java-development-eng.md +3 -3
- package/ENG/nodejs-backend-development-eng.md +8 -4
- package/ENG/php-development-eng.md +3 -3
- package/ENG/python-development-eng.md +205 -0
- package/ENG/rust-development-eng.md +42 -1
- package/ENG/sql-development-eng.md +4 -4
- package/ENG/swift-development-eng.md +5 -4
- package/ENG/test-code-eng.md +1 -1
- package/ENG/typescript-development-eng.md +4 -4
- package/GUIDE_ROUTER.md +6 -0
- package/LICENSE +133 -21
- package/LOOP_SYSTEM_DESIGN.md +10 -10
- package/README.md +28 -39
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/completions/_forgeloop +11 -3
- package/completions/forgeloop.bash +19 -3
- package/completions/forgeloop.fish +61 -1
- package/docs/AGENT_PROTOCOL_SUMMARY.md +50 -40
- package/docs/ARTIFACT_REFERENCE.md +50 -37
- package/docs/CLI_REFERENCE.md +147 -3
- package/docs/DOCUMENTATION_GUIDE.md +6 -4
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/PACKAGE_CONTENTS.md +14 -13
- package/docs/RELEASE_CHECKLIST.md +81 -10
- package/docs/SELF_HOSTED_TESTS.md +121 -0
- package/docs/documentation-manifest.json +25 -12
- package/package.json +15 -10
- package/schemas/task-bundle.schema.json +7 -2
- package/scripts/generate-agent-protocol-summary.mjs +2 -2
- package/scripts/run-mcp-clean-tests.mjs +30 -0
- package/src/adapters/agent-browser/provider.js +14 -11
- package/src/adapters/emulated-services/provider.js +11 -4
- package/src/adapters/temporary-paths.js +18 -0
- package/src/cli.js +146 -210
- package/src/commands/action-show.js +5 -2
- package/src/commands/advance.js +5 -1
- package/src/commands/baseline.js +13 -11
- package/src/commands/checkpoint-revalidate.js +24 -8
- package/src/commands/complete.js +19 -3
- package/src/commands/contract-create.js +3 -1
- package/src/commands/doctor.js +36 -13
- package/src/commands/gate-record.js +20 -31
- package/src/commands/gate-revalidate.js +11 -13
- package/src/commands/handoff-list.js +6 -1
- package/src/commands/handoff-show.js +6 -1
- package/src/commands/migrate-protocol.js +2 -2
- package/src/commands/model-route.js +9 -1
- package/src/commands/next.js +7 -1
- package/src/commands/policy-diff.js +25 -4
- package/src/commands/progress.js +17 -11
- package/src/commands/record-diagnosis.js +3 -0
- package/src/commands/semantic-plan.js +26 -2
- package/src/commands/status.js +6 -1
- package/src/commands/storage-backup.js +15 -0
- package/src/commands/storage-migrate.js +5 -0
- package/src/commands/storage-migration-resume.js +5 -0
- package/src/commands/storage-migration-status.js +44 -0
- package/src/commands/storage-restore-resume.js +7 -0
- package/src/commands/storage-restore.js +8 -0
- package/src/commands/task-abandon.js +1 -1
- package/src/commands/task-list.js +8 -43
- package/src/commands/task-migrate.js +3 -3
- package/src/commands/task-recover.js +10 -2
- package/src/commands/task-scope.js +18 -2
- package/src/commands/task-show.js +16 -2
- package/src/commands/validate-protocol.js +29 -14
- package/src/commands/validate-receipt.js +8 -1
- package/src/config/guides.json +4 -0
- package/src/core/action-ledger-projection.js +10 -3
- package/src/core/actions.js +38 -75
- package/src/core/activation.js +11 -3
- package/src/core/approvals.js +17 -51
- package/src/core/artifact-registry.js +59 -2
- package/src/core/artifacts.js +81 -27
- package/src/core/attachment-paths.js +45 -0
- package/src/core/attestation-coverage.js +6 -1
- package/src/core/attestation-verifier.js +21 -2
- package/src/core/attestation.js +17 -6
- package/src/core/audit.js +6 -1
- package/src/core/bundles.js +160 -66
- package/src/core/checkpoint-revalidation.js +12 -10
- package/src/core/cli-command-definitions.js +109 -7
- package/src/core/code-manifest.js +9 -3
- package/src/core/command-executors.js +182 -126
- package/src/core/command-input.js +21 -1
- package/src/core/completion-artifacts.js +71 -29
- package/src/core/completion-ownership.js +19 -8
- package/src/core/completion-recovery-rebind.js +11 -1
- package/src/core/completion.js +60 -9
- package/src/core/conformance.js +2 -1
- package/src/core/continuity-lint.js +13 -2
- package/src/core/continuity-reconciliation.js +16 -3
- package/src/core/continuity.js +16 -8
- package/src/core/contract-bootstrap-recovery.js +42 -41
- package/src/core/contract-revision.js +8 -9
- package/src/core/decision/artifact.js +2 -1
- package/src/core/decision/events.js +9 -6
- package/src/core/decision/resolver.js +19 -11
- package/src/core/decision/service.js +63 -38
- package/src/core/decision/task-bindings.js +6 -1
- package/src/core/diagnosis-model.js +11 -0
- package/src/core/diagnosis.js +10 -1
- package/src/core/diagnostic-projection.js +29 -12
- package/src/core/diagnostic-record.js +28 -3
- package/src/core/efficiency.js +6 -1
- package/src/core/events.js +316 -97
- package/src/core/execution-prerequisites.js +22 -13
- package/src/core/execution.js +26 -40
- package/src/core/filesystem.js +11 -0
- package/src/core/gate-artifact.js +105 -19
- package/src/core/gate-provenance.js +6 -5
- package/src/core/handoff.js +26 -14
- package/src/core/history.js +39 -35
- package/src/core/hypothesis-projection.js +32 -0
- package/src/core/information-gain-projection.js +104 -100
- package/src/core/inspect.js +12 -3
- package/src/core/integration-invocation-policy.js +10 -0
- package/src/core/integration-resources.js +41 -34
- package/src/core/ledger-event-collection.js +189 -0
- package/src/core/ledger-relations.js +23 -0
- package/src/core/native-storage.js +12 -0
- package/src/core/next-action-continuity.js +31 -9
- package/src/core/next-action-phases.js +1 -1
- package/src/core/next-action-review-phase.js +2 -2
- package/src/core/next-action-verification-phase.js +2 -1
- package/src/core/next-action.js +21 -2
- package/src/core/phase.js +138 -93
- package/src/core/policy-engine.js +12 -13
- package/src/core/preflight-consistency.js +72 -59
- package/src/core/preflight-loaders.js +19 -12
- package/src/core/preflight-model.js +6 -1
- package/src/core/preflight.js +29 -12
- package/src/core/progress.js +21 -10
- package/src/core/protocol-info.js +3 -0
- package/src/core/protocol-migration.js +7 -6
- package/src/core/reconcile-closure.js +162 -24
- package/src/core/recovery-history.js +66 -47
- package/src/core/reflection.js +72 -77
- package/src/core/responsibility.js +25 -13
- package/src/core/route-artifact.js +13 -3
- package/src/core/runtime-context.js +5 -0
- package/src/core/schema-validation.js +8 -3
- package/src/core/settlement.js +12 -1
- package/src/core/signing/bundle-file.js +53 -0
- package/src/core/signing/bundle-reference.js +15 -0
- package/src/core/signing/sigstore.js +60 -33
- package/src/core/signing/statement-file.js +27 -0
- package/src/core/storage-runtime-registry.js +41 -0
- package/src/core/structural-quality/artifacts.js +20 -14
- package/src/core/structural-quality/service.js +81 -56
- package/src/core/target-layout.js +1 -1
- package/src/core/task-claim-state.js +52 -18
- package/src/core/task-command.js +21 -1
- package/src/core/task-conflict-inspection.js +112 -107
- package/src/core/task-descriptor.js +29 -22
- package/src/core/task-discovery.js +288 -90
- package/src/core/task-lock.js +66 -306
- package/src/core/task-migration-validation.js +5 -5
- package/src/core/task-migration.js +17 -287
- package/src/core/task-snapshot.js +51 -8
- package/src/core/task-storage-migration.js +18 -0
- package/src/core/templates.js +20 -8
- package/src/core/test-intelligence/prune.js +5 -1
- package/src/core/trace.js +154 -99
- package/src/core/trajectory-evaluation.js +15 -3
- package/src/core/trajectory-metrics.js +47 -32
- package/src/core/transaction.js +16 -288
- package/src/core/verification-scope.js +20 -12
- package/src/core/work-state.js +100 -59
- package/src/repository-index/binary-manager.js +7 -5
- package/src/storage/artifact-bytes.js +22 -0
- package/src/storage/attachment-binding.js +11 -0
- package/src/storage/attachment-files.js +82 -0
- package/src/storage/attachment-references.js +36 -0
- package/src/storage/backup.js +53 -0
- package/src/storage/capabilities.js +31 -0
- package/src/storage/connection-owner.js +119 -0
- package/src/storage/connection.js +376 -0
- package/src/storage/existing-project-scope.js +45 -0
- package/src/storage/export-destination.js +44 -0
- package/src/storage/exporter.js +284 -0
- package/src/storage/file-durability.js +46 -0
- package/src/storage/importer.js +461 -0
- package/src/storage/index.js +74 -0
- package/src/storage/leases.js +63 -0
- package/src/storage/ledger-event-snapshot.js +150 -0
- package/src/storage/ledger-relations.js +220 -0
- package/src/storage/legacy-maintenance-boundary.js +18 -0
- package/src/storage/legacy-signature.js +115 -0
- package/src/storage/maintenance-handoff.js +129 -0
- package/src/storage/maintenance-owner.js +42 -0
- package/src/storage/maintenance.js +310 -0
- package/src/storage/metadata-json.js +35 -0
- package/src/storage/migration-activate.js +59 -0
- package/src/storage/migration-archive.js +57 -0
- package/src/storage/migration-attachment-root.js +61 -0
- package/src/storage/migration-candidate.js +398 -0
- package/src/storage/migration-publication.js +143 -0
- package/src/storage/migration-rollback-admission.js +54 -0
- package/src/storage/migration-rollback-preparation.js +132 -0
- package/src/storage/migration-rollback-publication.js +183 -0
- package/src/storage/migration-rollback-release.js +66 -0
- package/src/storage/migration-rollback-source-stage.js +103 -0
- package/src/storage/migration-rollback.js +43 -0
- package/src/storage/migration-source-partition.js +56 -0
- package/src/storage/migration-source.js +255 -0
- package/src/storage/migration-terminal.js +39 -0
- package/src/storage/migration-validation.js +75 -0
- package/src/storage/migration.js +43 -0
- package/src/storage/operational-attachments.js +49 -0
- package/src/storage/operational-context.js +50 -0
- package/src/storage/project-backup.js +98 -0
- package/src/storage/project-bootstrap.js +24 -0
- package/src/storage/project-boundary.js +79 -0
- package/src/storage/project-read-snapshot.js +18 -0
- package/src/storage/project-replacement.js +137 -0
- package/src/storage/project-restore.js +203 -0
- package/src/storage/record-definitions.js +45 -0
- package/src/storage/repository.js +507 -0
- package/src/storage/restore-journal.js +62 -0
- package/src/storage/restore-layout.js +30 -0
- package/src/storage/restore-preparation.js +84 -0
- package/src/storage/restore-replacement-archive.js +162 -0
- package/src/storage/restore-replacement-owner.js +31 -0
- package/src/storage/restore-replacement-preparation.js +128 -0
- package/src/storage/restore-replacement.js +142 -0
- package/src/storage/restore-terminal.js +32 -0
- package/src/storage/runtime.js +31 -0
- package/src/storage/schema.js +214 -0
- package/src/storage/singleton-import.js +84 -0
- package/src/storage/snapshot-continuity.js +16 -0
- package/src/storage/snapshot.js +36 -0
- package/src/storage/storage-marker.js +79 -0
- package/src/storage/task-guards.js +107 -0
- package/src/storage/topology.js +42 -0
- package/src/storage/transaction.js +179 -0
- package/src/storage/unit-of-work.js +663 -0
- package/src/storage/windows-process-incarnation.js +206 -0
- package/.forgeloop/forgeloop.gitignore +0 -4
- package/LICENSE-DOCS.md +0 -13
- package/src/core/transaction-maintenance.js +0 -70
|
@@ -1,13 +1,81 @@
|
|
|
1
1
|
# ForgeLoop Release Checklist
|
|
2
2
|
|
|
3
|
-
This is the current release checklist for
|
|
4
|
-
preparation and verification checklist;
|
|
3
|
+
This is the current release checklist for the PolyForm Noncommercial-licensed
|
|
4
|
+
`@cassiomc1/forgeloop` package. It is a preparation and verification checklist;
|
|
5
|
+
it does not authorize publication.
|
|
5
6
|
|
|
6
|
-
##
|
|
7
|
+
## Bounded 2.0.0-rc.2 publication
|
|
7
8
|
|
|
8
|
-
The
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
The owner approved short verification and an RC fallback on 2026-10-13. This
|
|
10
|
+
candidate is published only to npm `next`; stable `latest` remains blocked while
|
|
11
|
+
`soleSQLiteWriter` is false. A release candidate does not assert completed
|
|
12
|
+
migration acceptance or production readiness.
|
|
13
|
+
|
|
14
|
+
After npm rejected restricted RC1 publication with `E402`, the owner authorized
|
|
15
|
+
public core-package access. RC2 preserves the immutable RC1 tag; the MCP
|
|
16
|
+
companion is not published and retains restricted access.
|
|
17
|
+
|
|
18
|
+
`npm run release:quick` is the RC PR and publishing check. Each check group has
|
|
19
|
+
an enforced three-minute timeout; timeout or failure refuses release. It runs
|
|
20
|
+
source equivalence, writer inventory drift detection, essential SQLite boundary
|
|
21
|
+
and package-policy tests, lint, dependency policy, documentation conformance,
|
|
22
|
+
packed CLI/API/MCP smoke checks, hygiene, and secret scanning. This approved RC
|
|
23
|
+
path replaces a new local `verify:prepush` run. It does not replace the ordinary
|
|
24
|
+
or stable-release validation policy.
|
|
25
|
+
|
|
26
|
+
`scripts/check-rc-release.mjs` requires all 593 runtime/schema/integration files
|
|
27
|
+
to match baseline `14008f58f8ad255692997ce3fff359591fb695d1`. Root package
|
|
28
|
+
metadata may change only its RC version, the three approved RC scripts, and owner-authorized public access.
|
|
29
|
+
Resolved dependency locks must be unchanged apart from local package versions;
|
|
30
|
+
MCP metadata may only admit the explicit RC peer and bind the local core version.
|
|
31
|
+
Changed runtime or dependency bytes refuse this fast path.
|
|
32
|
+
|
|
33
|
+
The baseline Windows full-suite receipt is GitHub run `38483385848`, successful
|
|
34
|
+
on that exact source. Publishing verifies its repository, workflow, source SHA,
|
|
35
|
+
and completed success status through the authenticated GitHub API. Current
|
|
36
|
+
Linux/packaged smoke and local macOS short checks are separate evidence. No new
|
|
37
|
+
full-coverage, full-platform, physical power-loss, or statistically qualified
|
|
38
|
+
p95 result is claimed by the short path. The existing 1,000-task parity run
|
|
39
|
+
`38483369676` is retained for review; no fresh 200-repeat benchmark is dispatched.
|
|
40
|
+
|
|
41
|
+
The writer inventory covers every current `src` JavaScript module and records
|
|
42
|
+
imports plus potential write sites. Alias, computed-path, external-caller, and
|
|
43
|
+
transitive-authority review remain incomplete. The inventory explicitly keeps
|
|
44
|
+
`wholeConsumerClosure: false`; it prevents drift and does not authorize setting
|
|
45
|
+
`soleSQLiteWriter` to true.
|
|
46
|
+
|
|
47
|
+
RC release sequence: reviewed PR checks pass, merge preserving baseline
|
|
48
|
+
ancestry, tag `v2.0.0-rc.2`, dispatch `npm-publish.yml` with channel `next` and
|
|
49
|
+
confirmation `PUBLISH PUBLIC PACKAGE`, then verify npm version, Git commit,
|
|
50
|
+
tarball integrity, and provenance. Stable publishing retains full checks and
|
|
51
|
+
additionally refuses incomplete sole-writer acceptance.
|
|
52
|
+
|
|
53
|
+
## ForgeLoop 2.0.0 candidate scope
|
|
54
|
+
|
|
55
|
+
The 2.0.0 candidate is not release-ready while SQLite migration plan §10 has
|
|
56
|
+
open consumer-closure, current-source performance, or platform evidence. Keep
|
|
57
|
+
all acceptance boxes open until their evidence is bound to the exact candidate
|
|
58
|
+
revision.
|
|
59
|
+
|
|
60
|
+
The 2.0.0 candidate carries the SQLite storage transition alongside the
|
|
61
|
+
existing protocol, routing, provider, documentation, and package surfaces. The
|
|
62
|
+
existing checklist remains applicable to those surfaces; SQLite release
|
|
63
|
+
acceptance additionally requires every item below:
|
|
64
|
+
|
|
65
|
+
- [ ] All plan §9 acceptance rows are current for the exact candidate SHA,
|
|
66
|
+
including an accepted 1,000-task hosted parity run with exact output,
|
|
67
|
+
validated workers, clean observer admission, cleanup, and lower native
|
|
68
|
+
p95 for both operations.
|
|
69
|
+
- [ ] Plan §10 consumer closure is complete and `soleSQLiteWriter` is true
|
|
70
|
+
based on source audit and tests; fail-closed migration boundaries remain
|
|
71
|
+
in place until that evidence exists.
|
|
72
|
+
- [ ] Migration, crash recovery, legacy-unbound handoff, attachment recovery,
|
|
73
|
+
backup, restore, rollback, and SQLite integrity checks pass on the exact
|
|
74
|
+
candidate.
|
|
75
|
+
- [ ] Required Linux, macOS, Windows, supported Node runtime, MCP, API, and
|
|
76
|
+
package workflows pass at the exact candidate SHA.
|
|
77
|
+
- [ ] Release identity and npm version availability are verified, and the
|
|
78
|
+
matching tag workflow completes its verification job before publishing.
|
|
11
79
|
|
|
12
80
|
- [ ] README catalog and architecture fallback identify
|
|
13
81
|
`ENG/flutter-development-eng.md` and explain that the specialist is
|
|
@@ -41,9 +109,10 @@ boundaries explicit:
|
|
|
41
109
|
- [ ] Lifecycle, evidence, completion, and publication remain independent of
|
|
42
110
|
Repository Index and persistent-host health.
|
|
43
111
|
|
|
44
|
-
This
|
|
45
|
-
|
|
46
|
-
|
|
112
|
+
This checklist does not authorize publication by itself. The owner authorized
|
|
113
|
+
conditional npm publication through the GitHub workflow; the action remains
|
|
114
|
+
prohibited until every release gate passes. Tagging, GitHub Release,
|
|
115
|
+
deployment, and live data migration remain separate actions.
|
|
47
116
|
|
|
48
117
|
## CI minimization validation
|
|
49
118
|
|
|
@@ -76,7 +145,9 @@ actions.
|
|
|
76
145
|
## Contract and package identity
|
|
77
146
|
|
|
78
147
|
- [ ] `package.json` and `package-lock.json` contain the same package version.
|
|
79
|
-
- [ ] The package metadata declares the intended
|
|
148
|
+
- [ ] The package metadata declares the intended license
|
|
149
|
+
(`PolyForm-Noncommercial-1.0.0`), includes the official license text,
|
|
150
|
+
and retains public core-package publication access.
|
|
80
151
|
- [ ] `PROTOCOL_VERSION` and the integration API version remain compatible.
|
|
81
152
|
- [ ] `npm run release:identity` passes for the candidate version.
|
|
82
153
|
- [ ] No release tag or registry version collision exists.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Self-hosted Node validation
|
|
2
|
+
|
|
3
|
+
Node validation uses the following repository-specific runners. Platform and
|
|
4
|
+
Node-version matrix rows remain explicit; moving a job does not remove a check.
|
|
5
|
+
|
|
6
|
+
| Platform | Runner label | Execution location |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| macOS arm64 | `forgeloop-macos-local` | Local macOS LaunchAgent |
|
|
9
|
+
| Windows x64 | `forgeloop-windows-remote` | Windows service on the requested remote host |
|
|
10
|
+
| Linux x64 | `forgeloop-linux-remote` | Docker Linux container on that Windows host |
|
|
11
|
+
|
|
12
|
+
Publishing jobs retain their existing hosted execution and manual confirmation.
|
|
13
|
+
Their Node tests run in a preceding remote `verify` job. Classification and the
|
|
14
|
+
final PR result aggregation remain hosted; they do not run the product test
|
|
15
|
+
suite. CodeQL, dependency review and release-note publication retain their
|
|
16
|
+
existing execution locations.
|
|
17
|
+
|
|
18
|
+
`PR Core` also supports manual dispatch on a pushed candidate branch. Dispatch
|
|
19
|
+
selects every validation category, so validation does not require a PR event
|
|
20
|
+
or revision-range payload. PR invocations keep their existing path routing.
|
|
21
|
+
The native Repository Index matrix waits for the preceding Linux checks to
|
|
22
|
+
finish and runs one platform row at a time. Expanded Node compatibility waits
|
|
23
|
+
for its minimum-runtime check and also runs one row at a time. Native rows still
|
|
24
|
+
run after an earlier test failure to retain diagnostic coverage; final required
|
|
25
|
+
validation reports preceding failures.
|
|
26
|
+
|
|
27
|
+
Dispatch Windows full suite, expanded Node compatibility, and PR Core
|
|
28
|
+
sequentially when validating the shared Windows/Linux host. The Windows job
|
|
29
|
+
checks that the requested host IP is present and logs only the Docker runner's
|
|
30
|
+
container ID, image ID, running state and hostname. Core Linux jobs record their
|
|
31
|
+
platform, architecture, hostname and checked-out revision. Match the Linux
|
|
32
|
+
hostname to the Windows Docker inspection before claiming the requested
|
|
33
|
+
execution location. These identity checks remain unverified until the candidate
|
|
34
|
+
workflows actually execute.
|
|
35
|
+
|
|
36
|
+
PR repository-hygiene validation runs in the guarded remote Linux `lint` job,
|
|
37
|
+
including documentation-only changes. Its checkout and Node prerequisites run
|
|
38
|
+
for every admitted PR; JavaScript lint and dependency installation retain their
|
|
39
|
+
path conditions. Hosted classification computes routing outputs only.
|
|
40
|
+
|
|
41
|
+
Required ruleset check names remain unchanged. The historical
|
|
42
|
+
`tarball smoke (ubuntu-latest)` context now executes in the remote Linux
|
|
43
|
+
container; the check name is retained for ruleset compatibility.
|
|
44
|
+
|
|
45
|
+
## Admission
|
|
46
|
+
|
|
47
|
+
This is a public repository. An operator-owned copy of
|
|
48
|
+
`scripts/ci-runner-admission.mjs` runs through
|
|
49
|
+
`ACTIONS_RUNNER_HOOK_JOB_STARTED` before checkout. The shell wrappers and admission
|
|
50
|
+
module live outside the job checkout and runner application directory. Keep
|
|
51
|
+
those deployed copies synchronized when changing the policy.
|
|
52
|
+
|
|
53
|
+
Admission requires matching repository metadata and allows only `push`,
|
|
54
|
+
`workflow_dispatch`, `schedule`, `release`, and pull requests whose base and head
|
|
55
|
+
both belong to this repository. Fork pull requests, `pull_request_target`,
|
|
56
|
+
unknown events, missing metadata and invalid event files fail before job steps.
|
|
57
|
+
Workflow job conditions independently exclude fork pull requests. Fork test
|
|
58
|
+
validation requires a separate isolated execution environment; these machines
|
|
59
|
+
do not run fork contributions.
|
|
60
|
+
|
|
61
|
+
The hook does not sandbox admitted code. Repository write access is therefore a
|
|
62
|
+
trust boundary. The Linux runner container has no Windows filesystem mounts or
|
|
63
|
+
Docker socket mount. Its workspace and GitHub-managed runner credentials stay
|
|
64
|
+
inside the dedicated container. Native macOS and Windows jobs run with their
|
|
65
|
+
configured local accounts.
|
|
66
|
+
|
|
67
|
+
## Installed locations and status
|
|
68
|
+
|
|
69
|
+
Local macOS:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
cd /Users/cassio/.local/share/forgeloop-ci/macos
|
|
73
|
+
./svc.sh status
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Windows PowerShell:
|
|
77
|
+
|
|
78
|
+
```powershell
|
|
79
|
+
Get-Service actions.runner.cassiomc1-forgeloop.forgeloop-windows-remote
|
|
80
|
+
docker inspect forgeloop-linux-remote --format '{{json .State}}'
|
|
81
|
+
docker logs --tail 40 forgeloop-linux-remote
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The Windows runner is installed at `C:\forgeloop-ci\windows`. Its deployed hook
|
|
85
|
+
is `C:\forgeloop-ci\hooks\windows-admission.ps1`. Isolated runtimes live below
|
|
86
|
+
`C:\forgeloop-ci\runtime`; the runner environment includes portable Node, Git,
|
|
87
|
+
Bash and unzip. Runner service credentials are managed by Windows; no account
|
|
88
|
+
password belongs in repository files, job logs or shell scripts.
|
|
89
|
+
|
|
90
|
+
The Linux image is built from `.github/runner/Dockerfile` using the repository
|
|
91
|
+
root as its build context:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
docker build -f .github/runner/Dockerfile -t forgeloop-actions-linux:2.337.0 .
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The Node base image is pinned by digest. The GitHub runner archive is checked
|
|
98
|
+
against its published SHA-256 before extraction. System dependency packages are
|
|
99
|
+
resolved by the official runner dependency installer at build time; rebuilding
|
|
100
|
+
is not a claim of byte-identical package resolution. The deployed image and
|
|
101
|
+
runner version must be recorded with validation evidence.
|
|
102
|
+
|
|
103
|
+
Docker Desktop credential helpers can fail in a noninteractive WinRM session.
|
|
104
|
+
The dedicated `C:\forgeloop-ci\docker-config` uses an empty public Docker Hub auth
|
|
105
|
+
entry and the already-installed Docker Desktop CLI plugin directory. Commands
|
|
106
|
+
using that configuration target the existing Linux engine explicitly. They do
|
|
107
|
+
not modify the user's Docker credential store.
|
|
108
|
+
|
|
109
|
+
Registration tokens expire and must be obtained through an authorized GitHub
|
|
110
|
+
operator. Pass them transiently through `ACTIONS_RUNNER_INPUT_TOKEN`; do not
|
|
111
|
+
store registration tokens in Docker image layers, repository scripts or a
|
|
112
|
+
container's persistent environment. A stopped or offline runner is not evidence
|
|
113
|
+
that its checks passed.
|
|
114
|
+
|
|
115
|
+
## Verification boundary
|
|
116
|
+
|
|
117
|
+
Runner registration, admission probes, a native SQLite smoke, quick suites,
|
|
118
|
+
full core suites, MCP/package checks and GitHub workflow execution are separate
|
|
119
|
+
evidence dimensions. Record the source fingerprint and exact command for each
|
|
120
|
+
run. A workflow routing edit becomes externally verified only after GitHub
|
|
121
|
+
executes the affected jobs on these runners.
|
|
@@ -199,18 +199,6 @@
|
|
|
199
199
|
],
|
|
200
200
|
"packaged": true
|
|
201
201
|
},
|
|
202
|
-
{
|
|
203
|
-
"path": "LICENSE-DOCS.md",
|
|
204
|
-
"id": "documentation-license",
|
|
205
|
-
"class": "legal",
|
|
206
|
-
"audience": [
|
|
207
|
-
"maintainer"
|
|
208
|
-
],
|
|
209
|
-
"canonicalFor": [
|
|
210
|
-
"documentation-license"
|
|
211
|
-
],
|
|
212
|
-
"packaged": true
|
|
213
|
-
},
|
|
214
202
|
{
|
|
215
203
|
"path": ".github/copilot-instructions.md",
|
|
216
204
|
"id": "copilot-adapter",
|
|
@@ -1287,6 +1275,19 @@
|
|
|
1287
1275
|
],
|
|
1288
1276
|
"packaged": true
|
|
1289
1277
|
},
|
|
1278
|
+
{
|
|
1279
|
+
"path": "ENG/python-development-eng.md",
|
|
1280
|
+
"id": "guide-python",
|
|
1281
|
+
"class": "guide",
|
|
1282
|
+
"audience": [
|
|
1283
|
+
"harness",
|
|
1284
|
+
"maintainer"
|
|
1285
|
+
],
|
|
1286
|
+
"canonicalFor": [
|
|
1287
|
+
"guide-python"
|
|
1288
|
+
],
|
|
1289
|
+
"packaged": true
|
|
1290
|
+
},
|
|
1290
1291
|
{
|
|
1291
1292
|
"path": "ENG/swift-development-eng.md",
|
|
1292
1293
|
"id": "guide-swift",
|
|
@@ -1392,6 +1393,18 @@
|
|
|
1392
1393
|
"provider-source-boundary"
|
|
1393
1394
|
],
|
|
1394
1395
|
"packaged": true
|
|
1396
|
+
},
|
|
1397
|
+
{
|
|
1398
|
+
"path": "docs/SELF_HOSTED_TESTS.md",
|
|
1399
|
+
"id": "self-hosted-tests",
|
|
1400
|
+
"class": "operational",
|
|
1401
|
+
"audience": [
|
|
1402
|
+
"maintainer"
|
|
1403
|
+
],
|
|
1404
|
+
"canonicalFor": [
|
|
1405
|
+
"self-hosted-node-validation"
|
|
1406
|
+
],
|
|
1407
|
+
"packaged": true
|
|
1395
1408
|
}
|
|
1396
1409
|
]
|
|
1397
1410
|
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cassiomc1/forgeloop",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0-rc.2",
|
|
4
|
+
"description": "Evidence-first engineering protocol for AI coding environments and developer workflows",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/cassiomc1/forgeloop.git"
|
|
8
8
|
},
|
|
9
9
|
"homepage": "https://github.com/cassiomc1/forgeloop#readme",
|
|
10
|
-
"license": "
|
|
10
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
14
|
"type": "module",
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
16
|
+
"node": ">=24.19.0"
|
|
17
17
|
},
|
|
18
18
|
"bin": {
|
|
19
19
|
"forgeloop": "src/cli.js"
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"benchmarks/repository-index/README.md",
|
|
29
29
|
"benchmarks/repository-index/run-hot-path.mjs",
|
|
30
30
|
"benchmarks/repository-index/run-persistent-transport.mjs",
|
|
31
|
-
".forgeloop/forgeloop.gitignore",
|
|
32
31
|
"AGENTS.md",
|
|
33
32
|
"CLAUDE.md",
|
|
34
33
|
"GUIDE_ROUTER.md",
|
|
@@ -45,8 +44,6 @@
|
|
|
45
44
|
"CONTRACT_COVERAGE.md",
|
|
46
45
|
"PROJECT_PROFILE.md",
|
|
47
46
|
"THIRD_PARTY_NOTICES.md",
|
|
48
|
-
"LICENSE",
|
|
49
|
-
"LICENSE-DOCS.md",
|
|
50
47
|
".cursor",
|
|
51
48
|
".github/copilot-instructions.md",
|
|
52
49
|
"README.md",
|
|
@@ -68,6 +65,7 @@
|
|
|
68
65
|
"docs/STRUCTURAL_QUALITY.md",
|
|
69
66
|
"docs/DOCUMENTATION_GUIDE.md",
|
|
70
67
|
"docs/RELEASE_CHECKLIST.md",
|
|
68
|
+
"docs/SELF_HOSTED_TESTS.md",
|
|
71
69
|
"docs/PACKAGE_CONTENTS.md",
|
|
72
70
|
"docs/PROVIDER_ARCHITECTURE.md",
|
|
73
71
|
"docs/PROVIDERS.md",
|
|
@@ -121,7 +119,9 @@
|
|
|
121
119
|
"!src/core/gates.js",
|
|
122
120
|
"!src/core/decision-classification.js",
|
|
123
121
|
"!src/core/workflow-compatibility.js",
|
|
124
|
-
"!src/core/cli-metadata.js"
|
|
122
|
+
"!src/core/cli-metadata.js",
|
|
123
|
+
"LICENSE",
|
|
124
|
+
"scripts/run-mcp-clean-tests.mjs"
|
|
125
125
|
],
|
|
126
126
|
"scripts": {
|
|
127
127
|
"test": "node scripts/run-tests.js",
|
|
@@ -166,11 +166,12 @@
|
|
|
166
166
|
"benchmark:profiles:outliers": "node scripts/report-execution-profile-outliers.mjs",
|
|
167
167
|
"benchmark:profiles:tail-analysis": "node scripts/report-tail-interpretation.mjs",
|
|
168
168
|
"benchmark:profiles:regression": "node scripts/check-efficiency-regression.mjs",
|
|
169
|
-
"transactions:compact": "node scripts/compact-transactions.mjs",
|
|
170
169
|
"complexity:check": "node scripts/check-complexity.mjs",
|
|
171
170
|
"repository-index:manifest": "node scripts/verify-tgrep-manifest.mjs",
|
|
172
171
|
"repository:hygiene": "node scripts/check-repository-hygiene.mjs",
|
|
173
172
|
"task-discovery:benchmark": "node scripts/benchmark-task-discovery.mjs",
|
|
173
|
+
"storage:benchmark": "node scripts/benchmark-storage-sqlite.mjs",
|
|
174
|
+
"storage:benchmark:diagnosis": "node scripts/benchmark-diagnosis-parity.mjs",
|
|
174
175
|
"verify:fast": "node scripts/run-validation.mjs --tier fast",
|
|
175
176
|
"verify:local": "node scripts/run-validation.mjs --tier local",
|
|
176
177
|
"verify:prepush": "node scripts/run-validation.mjs --tier prepush",
|
|
@@ -178,7 +179,11 @@
|
|
|
178
179
|
"jev:smoke": "node scripts/jev-smoke.mjs",
|
|
179
180
|
"benchmark:jev": "node scripts/benchmark-jev.mjs",
|
|
180
181
|
"benchmark:jev:live": "node scripts/benchmark-jev.mjs --live",
|
|
181
|
-
"benchmark:test-intelligence": "node scripts/benchmark-test-intelligence.mjs"
|
|
182
|
+
"benchmark:test-intelligence": "node scripts/benchmark-test-intelligence.mjs",
|
|
183
|
+
"mcp:test:clean": "node scripts/run-mcp-clean-tests.mjs",
|
|
184
|
+
"release:quick": "node scripts/run-rc-release.mjs",
|
|
185
|
+
"release:rc:check": "node scripts/check-rc-release.mjs",
|
|
186
|
+
"storage:writer-inventory": "node scripts/storage-writer-inventory.mjs"
|
|
182
187
|
},
|
|
183
188
|
"devDependencies": {
|
|
184
189
|
"c8": "^12.0.0",
|
|
@@ -5,10 +5,15 @@
|
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": ["schemaVersion", "protocolVersion", "taskId", "artifacts"],
|
|
7
7
|
"properties": {
|
|
8
|
-
"schemaVersion": { "
|
|
8
|
+
"schemaVersion": { "enum": [1, 2] },
|
|
9
9
|
"protocolVersion": { "const": 1 },
|
|
10
10
|
"taskId": { "type": "string", "minLength": 1 },
|
|
11
|
-
"artifacts": { "type": "array", "items": { "type": "string", "minLength": 1 } }
|
|
11
|
+
"artifacts": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
12
|
+
"files": { "type": "array", "items": { "type": "object", "required": ["path", "size", "sha256"], "properties": { "path": { "type": "string", "minLength": 1 }, "size": { "type": "integer", "minimum": 0 }, "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" } }, "additionalProperties": false } }
|
|
12
13
|
},
|
|
14
|
+
"oneOf": [
|
|
15
|
+
{ "properties": { "schemaVersion": { "const": 1 } } },
|
|
16
|
+
{ "properties": { "schemaVersion": { "const": 2 } }, "required": ["files"] }
|
|
17
|
+
],
|
|
13
18
|
"additionalProperties": false
|
|
14
19
|
}
|
|
@@ -79,7 +79,7 @@ capability-family versions.`;
|
|
|
79
79
|
const artifacts = Object.values(ARTIFACT_REGISTRY)
|
|
80
80
|
.filter((artifact) => artifact.isPublic)
|
|
81
81
|
.sort((left, right) => left.key.localeCompare(right.key))
|
|
82
|
-
.map((artifact) => [artifact.key, artifact.scope, artifact.path, artifact.schema, artifact.trustRole]);
|
|
82
|
+
.map((artifact) => [artifact.key, artifact.scope, artifact.logicalPath, `${artifact.canonicalStorage.path}${artifact.canonicalStorage.table ? ` :: ${artifact.canonicalStorage.table}` : ""}`, artifact.exportPath ?? "Copied separately", artifact.schema, artifact.trustRole]);
|
|
83
83
|
const errors = Object.values(PUBLIC_ERROR_REGISTRY)
|
|
84
84
|
.filter((error) => /^(E_(CLI|WORKSPACE|HANDOFF|RESPONSIBILITY|VERIFICATION_SCOPE|REVISION|ATTESTATION)_)/u.test(error.code))
|
|
85
85
|
.sort((left, right) => left.code.localeCompare(right.code))
|
|
@@ -192,7 +192,7 @@ ${guideCatalog()}
|
|
|
192
192
|
|
|
193
193
|
## Public artifact registry
|
|
194
194
|
|
|
195
|
-
${table(["Key", "Scope", "
|
|
195
|
+
${table(["Key", "Scope", "Logical identity", "Canonical storage", "Export path", "Schema", "Trust role"], artifacts)}
|
|
196
196
|
|
|
197
197
|
## Command catalog
|
|
198
198
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cpSync, mkdirSync, mkdtempSync, rmSync, symlinkSync } from "node:fs";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { installLockedMcp } from "./mcp-locked-install.mjs";
|
|
8
|
+
import { runMcpTests } from "./run-mcp-tests.mjs";
|
|
9
|
+
import { runNpm } from "./npm-command.mjs";
|
|
10
|
+
|
|
11
|
+
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
|
+
const temporary = mkdtempSync(path.join(os.tmpdir(), "forgeloop-mcp-clean-"));
|
|
13
|
+
try {
|
|
14
|
+
const packed = JSON.parse(runNpm(["pack", "--json", "--pack-destination", temporary], { cwd: repositoryRoot, encoding: "utf8" }));
|
|
15
|
+
const coreTarball = path.join(temporary, packed[0].filename);
|
|
16
|
+
const root = path.join(temporary, "project");
|
|
17
|
+
mkdirSync(root);
|
|
18
|
+
execFileSync("tar", ["-xzf", coreTarball, "--strip-components=1", "-C", root]);
|
|
19
|
+
const mcpRoot = path.join(root, "integrations/mcp");
|
|
20
|
+
mkdirSync(mcpRoot, { recursive: true });
|
|
21
|
+
for (const relative of ["src", "bin", "tests", "README.md", "LICENSE"]) cpSync(path.join(repositoryRoot, "integrations/mcp", relative), path.join(mcpRoot, relative), { recursive: true });
|
|
22
|
+
for (const relative of ["tests/helpers", "tests/fixtures"]) cpSync(path.join(repositoryRoot, relative), path.join(root, relative), { recursive: true });
|
|
23
|
+
installLockedMcp({ target: mcpRoot, tarballs: [coreTarball] });
|
|
24
|
+
// Source fixtures and the installed package use the same locked external
|
|
25
|
+
// dependencies without borrowing the user's existing node_modules.
|
|
26
|
+
symlinkSync(path.join(mcpRoot, "node_modules"), path.join(root, "node_modules"), process.platform === "win32" ? "junction" : "dir");
|
|
27
|
+
process.exitCode = await runMcpTests({ root: mcpRoot });
|
|
28
|
+
} finally {
|
|
29
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
30
|
+
}
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { physicalTemporaryPath } from "../temporary-paths.js";
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
E_BROWSER_VERIFICATION_ORIGIN_DENIED,
|
|
@@ -74,7 +75,7 @@ function assertBooleanObservation(data, label) {
|
|
|
74
75
|
|
|
75
76
|
function isWithin(parent, child) {
|
|
76
77
|
const relative = path.relative(path.resolve(parent), path.resolve(child));
|
|
77
|
-
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== "..");
|
|
78
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
function assertTempRootOutsideTarget(root, target) {
|
|
@@ -96,18 +97,20 @@ async function createCwd(tempRoot, fsImpl, target) {
|
|
|
96
97
|
const root = tempRoot ?? os.tmpdir();
|
|
97
98
|
if (typeof root !== "string" || !path.isAbsolute(root)) throw providerError(E_BROWSER_VERIFICATION_PROVIDER_INVALID, "Agent Browser tempRoot must be absolute");
|
|
98
99
|
assertTempRootOutsideTarget(root, target);
|
|
100
|
+
const physicalRoot = await physicalTemporaryPath(root);
|
|
101
|
+
const physicalTarget = typeof target === "string" && path.isAbsolute(target) ? await physicalTemporaryPath(target) : target;
|
|
102
|
+
assertTempRootOutsideTarget(physicalRoot, physicalTarget);
|
|
99
103
|
const make = fsImpl?.mkdtemp ?? mkdtemp;
|
|
100
|
-
const cwd = await make(path.join(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
throw providerError(E_BROWSER_VERIFICATION_PROVIDER_INVALID, "Agent Browser temporary cwd is invalid");
|
|
104
|
-
}
|
|
105
|
-
assertTempRootOutsideTarget(cwd, target);
|
|
106
|
-
} catch (error) {
|
|
107
|
-
try { await fsImpl?.rm?.(cwd, { recursive: true, force: true }); } catch { /* preserve the boundary failure */ }
|
|
108
|
-
throw error;
|
|
104
|
+
const cwd = await make(path.join(physicalRoot, "forgeloop-agent-browser-"));
|
|
105
|
+
if (typeof cwd !== "string" || !path.isAbsolute(cwd)) {
|
|
106
|
+
throw providerError(E_BROWSER_VERIFICATION_PROVIDER_INVALID, "Agent Browser temporary cwd is invalid");
|
|
109
107
|
}
|
|
110
|
-
|
|
108
|
+
assertTempRootOutsideTarget(cwd, target);
|
|
109
|
+
const physicalCwd = await physicalTemporaryPath(cwd);
|
|
110
|
+
assertTempRootOutsideTarget(physicalCwd, physicalTarget);
|
|
111
|
+
// A rejected returned path is not owned cleanup authority. Only a validated
|
|
112
|
+
// private cwd reaches the caller's process execution and cleanup block.
|
|
113
|
+
return physicalCwd;
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
function screenshotRef(verificationId, digest) { return `agent-browser/${verificationId}/${digest}.png`; }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { physicalTemporaryPath } from "../temporary-paths.js";
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
EMULATED_SERVICES_ERROR_CODES,
|
|
@@ -202,15 +203,21 @@ export function createEmulatedServicesProvider(options = {}) {
|
|
|
202
203
|
const normalized = normalizeRequest(request, { fsImpl, targetRoot });
|
|
203
204
|
validateSignal(normalized.signal);
|
|
204
205
|
assertNotCancelled(normalized.signal);
|
|
205
|
-
const tempPrefix = path.join(tempRoot, "forgeloop-emulated-");
|
|
206
206
|
let stateRoot = null;
|
|
207
207
|
let managed = null;
|
|
208
208
|
let result = null;
|
|
209
209
|
let operationError = null;
|
|
210
210
|
try {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
assertTempRootSafe(
|
|
211
|
+
const physicalRoot = await physicalTemporaryPath(tempRoot);
|
|
212
|
+
const physicalTarget = targetRoot ? await physicalTemporaryPath(targetRoot) : null;
|
|
213
|
+
assertTempRootSafe(physicalRoot, physicalTarget);
|
|
214
|
+
const candidate = await mkdtempImpl(path.join(physicalRoot, "forgeloop-emulated-"));
|
|
215
|
+
assertAbsolutePath(candidate, "Emulated Services temporary state");
|
|
216
|
+
assertTempRootSafe(candidate, targetRoot);
|
|
217
|
+
const physicalState = await physicalTemporaryPath(candidate);
|
|
218
|
+
assertTempRootSafe(physicalState, physicalTarget);
|
|
219
|
+
// Rejected paths do not grant recursive cleanup authority.
|
|
220
|
+
stateRoot = physicalState;
|
|
214
221
|
const versionResult = await runBoundedEmulatedServicesCommand(executablePath, ["--version"], {
|
|
215
222
|
cwd: stateRoot,
|
|
216
223
|
env: filteredEnvironment(),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { realpathWithTransientWindowsRetry } from "../core/filesystem.js";
|
|
3
|
+
|
|
4
|
+
/** Resolve existing ancestors even when a configured target does not exist yet. */
|
|
5
|
+
export async function physicalTemporaryPath(filename) {
|
|
6
|
+
let current = path.resolve(filename);
|
|
7
|
+
const missing = [];
|
|
8
|
+
while (true) {
|
|
9
|
+
try { return path.join(await realpathWithTransientWindowsRetry(current), ...missing); }
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code !== "ENOENT") throw error;
|
|
12
|
+
const parent = path.dirname(current);
|
|
13
|
+
if (parent === current) throw error;
|
|
14
|
+
missing.unshift(path.basename(current));
|
|
15
|
+
current = parent;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|