forge-workflow 0.1.0-beta.5 → 0.1.0-beta.7
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/AGENTS.md +4 -0
- package/CHANGELOG.md +48 -0
- package/CLAUDE.md +0 -12
- package/CODING_STANDARDS.md +72 -0
- package/bin/forge.js +12 -1
- package/docs/guides/MIGRATION.md +3 -3
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
- package/docs/reference/INSTALL.md +4 -0
- package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
- package/docs/reference/RELEASE.md +4 -4
- package/docs/reference/github-accounts.md +134 -0
- package/docs/reference/shepherd.md +63 -13
- package/lib/adapters/pr-state-adapter.js +15 -2
- package/lib/base-remote.js +138 -0
- package/lib/beta5-compatibility-evidence.js +1093 -0
- package/lib/bun-lockfile-proof.js +413 -0
- package/lib/bun-workflow-pins.js +461 -0
- package/lib/capabilities/index.js +9 -0
- package/lib/capabilities/model.js +141 -0
- package/lib/capabilities/probes.js +347 -0
- package/lib/codex-skills.js +2 -2
- package/lib/commands/_manifest.js +1 -0
- package/lib/commands/_registry.js +48 -18
- package/lib/commands/clean.js +57 -1
- package/lib/commands/doctor.js +37 -6
- package/lib/commands/gate.js +197 -27
- package/lib/commands/github.js +215 -0
- package/lib/commands/hooks.js +54 -6
- package/lib/commands/memory.js +66 -2
- package/lib/commands/merge.js +720 -73
- package/lib/commands/plan.js +33 -2
- package/lib/commands/pr.js +2 -0
- package/lib/commands/preflight.js +10 -2
- package/lib/commands/push.js +120 -9
- package/lib/commands/recall.js +95 -61
- package/lib/commands/release.js +23 -2
- package/lib/commands/remember.js +28 -4
- package/lib/commands/serve.js +26 -9
- package/lib/commands/setup.js +132 -4
- package/lib/commands/shepherd.js +578 -72
- package/lib/commands/ship.js +15 -69
- package/lib/commands/skill.js +8 -0
- package/lib/commands/team.js +47 -8
- package/lib/commands/test.js +163 -4
- package/lib/commands/validate.js +78 -22
- package/lib/commands/worktree.js +155 -19
- package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
- package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
- package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
- package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
- package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
- package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
- package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
- package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
- package/lib/forge-issues.js +78 -0
- package/lib/gate-events.js +98 -10
- package/lib/github-context.js +308 -0
- package/lib/global-flags.js +1 -0
- package/lib/hook-renderer.js +29 -1
- package/lib/issue-render.js +19 -0
- package/lib/kernel/broker.js +723 -31
- package/lib/kernel/claim-reconciler.js +238 -0
- package/lib/kernel/lease-enforcer.js +9 -4
- package/lib/kernel/legacy-claim-repair.js +442 -0
- package/lib/kernel/live-claim-projection.js +26 -0
- package/lib/kernel/migrations.js +118 -3
- package/lib/kernel/readiness-model.js +184 -12
- package/lib/kernel/schema.js +49 -1
- package/lib/kernel/sqlite-driver.js +3322 -183
- package/lib/kernel/taxonomy-validator.js +4 -1
- package/lib/kernel/windows-private-acl.js +239 -0
- package/lib/memory/hygiene.js +191 -0
- package/lib/memory/router.js +94 -27
- package/lib/memory/usage-evidence.js +4 -0
- package/lib/memory-digest.js +59 -0
- package/lib/merge-rules.js +135 -17
- package/lib/npm-publish-workflow.js +233 -40
- package/lib/package-root.js +2 -0
- package/lib/pr-monitor/auto-actions.js +169 -28
- package/lib/pr-monitor/differ.js +110 -4
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/flow-monitor.js +1424 -0
- package/lib/pr-monitor/gather.js +251 -44
- package/lib/pr-monitor/journal.js +0 -37
- package/lib/pr-monitor/monitor.js +117 -10
- package/lib/pr-monitor/process-identity.js +117 -0
- package/lib/pr-monitor/reconcile-executor.js +1101 -625
- package/lib/pr-monitor/reconcile.js +0 -0
- package/lib/pr-monitor/render-summary.js +121 -24
- package/lib/pr-monitor/review-preflight.js +269 -0
- package/lib/pr-monitor/shepherd-lease.js +28 -19
- package/lib/pr-monitor/verdict.js +438 -0
- package/lib/pr-monitor/watch-lifecycle.js +144 -38
- package/lib/pr-monitor/watch-owner.js +1414 -0
- package/lib/pr-monitor/watch.js +129 -58
- package/lib/pr-shepherd.js +17 -3
- package/lib/project-memory.js +145 -3
- package/lib/protected-state-authority.js +799 -4
- package/lib/protected-state-surfaces.js +181 -3
- package/lib/release-readiness.js +2 -3
- package/lib/review-adapter.js +65 -0
- package/lib/skills-sync.js +65 -32
- package/lib/validation/risk-manifest.js +339 -0
- package/lib/validation-receipt.js +190 -0
- package/lib/workflow/enforce-stage.js +44 -0
- package/lib/workflow/plan-authority.js +225 -0
- package/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/node_modules/@forge/contracts/index.js +32 -0
- package/node_modules/@forge/contracts/package.json +35 -0
- package/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/node_modules/@forge/contracts/src/identity.js +42 -0
- package/node_modules/@forge/contracts/src/schema.js +72 -0
- package/node_modules/@forge/contracts/src/validate.js +305 -0
- package/node_modules/@forge/flow/index.js +114 -0
- package/node_modules/@forge/flow/package.json +34 -0
- package/node_modules/@forge/flow/src/bounded-loop.js +415 -0
- package/node_modules/@forge/flow/src/efficiency-supervisor.js +89 -0
- package/node_modules/@forge/flow/src/executor.js +279 -0
- package/node_modules/@forge/flow/src/monitor-durability.js +419 -0
- package/node_modules/@forge/flow/src/monitor-runtime.js +460 -0
- package/node_modules/@forge/flow/src/process-lifecycle.js +469 -0
- package/node_modules/@forge/flow/src/skill-runtime.js +343 -0
- package/node_modules/@forge/memory/index.js +331 -0
- package/node_modules/@forge/memory/package.json +34 -0
- package/node_modules/@forge/memory/src/authority-provider.js +67 -0
- package/node_modules/@forge/memory/src/backend-registry.js +166 -0
- package/node_modules/@forge/memory/src/feedback-intake.js +243 -0
- package/node_modules/@forge/memory/src/pr-lifecycle-authority.js +946 -0
- package/node_modules/@forge/memory/src/usage-evidence.js +205 -0
- package/package.json +16 -4
- package/packages/flow/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/index.js +32 -0
- package/packages/flow/node_modules/@forge/contracts/package.json +35 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/flow/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/flow/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/flow/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/flow/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/flow/node_modules/@forge/contracts/src/validate.js +305 -0
- package/packages/memory/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/index.js +32 -0
- package/packages/memory/node_modules/@forge/contracts/package.json +35 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/memory/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/memory/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/memory/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/memory/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/memory/node_modules/@forge/contracts/src/validate.js +305 -0
- package/scripts/commitlint.js +13 -15
- package/scripts/generate-risk-manifest.js +91 -0
- package/scripts/github-context-bridge.sh +10 -0
- package/scripts/legacy-claim-repair.js +145 -0
- package/scripts/lib/behavioral-eval-runtime.js +3 -2
- package/scripts/process-tree.js +14 -2
- package/scripts/protected-state-check.js +440 -17
- package/scripts/sync-agent-skills.js +333 -34
- package/scripts/test-full-suite.js +704 -18
- package/scripts/test-profile.js +13 -3
- package/scripts/test.js +96 -15
- package/skills/coverage.json +1 -0
- package/skills/review/SKILL.md +2 -0
- package/skills/review/evals/scorecard.json +2 -2
- package/skills/setup/SKILL.md +18 -0
- package/skills/setup/evals/scorecard.json +3 -3
- package/skills/shepherd/SKILL.md +19 -2
- package/skills/shepherd/evals/scorecard.json +3 -3
- package/skills/validate/SKILL.md +3 -0
- package/skills/validate/evals/scorecard.json +1 -1
package/lib/commands/validate.js
CHANGED
|
@@ -12,6 +12,7 @@ const { execFileSync } = require('node:child_process');
|
|
|
12
12
|
const fs = require('node:fs');
|
|
13
13
|
const path = require('node:path');
|
|
14
14
|
const { secureExecFileSync } = require('../shell-utils');
|
|
15
|
+
const validationReceipt = require('../validation-receipt');
|
|
15
16
|
|
|
16
17
|
// Constants
|
|
17
18
|
const CHECK_TYPES = {
|
|
@@ -44,10 +45,12 @@ const CONFLICT_DIVIDER_PATTERN = /^=======$/;
|
|
|
44
45
|
const CONFLICT_END_PATTERN = /^>>>>>>>.*$/;
|
|
45
46
|
const GIT_CONFLICT_SCAN_MAX_BUFFER = 16 * 1024 * 1024;
|
|
46
47
|
const VALIDATION_COMMAND_TIMEOUT_MS = 600000;
|
|
47
|
-
|
|
48
|
+
// Match the full-suite runner's measured headroom; its verbose output exceeds 1 MiB.
|
|
49
|
+
const FULL_SUITE_TIMEOUT_MS = 25 * 60 * 1000;
|
|
50
|
+
const FULL_SUITE_MAX_BUFFER = 16 * 1024 * 1024;
|
|
48
51
|
|
|
49
|
-
function validationTimeoutMessage(label) {
|
|
50
|
-
return `${label} timed out after ${
|
|
52
|
+
function validationTimeoutMessage(label, timeoutMs = VALIDATION_COMMAND_TIMEOUT_MS) {
|
|
53
|
+
return `${label} timed out after ${Math.round(timeoutMs / 60000)} minutes`;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
function listConflictScanFiles(rootDir) {
|
|
@@ -80,8 +83,15 @@ function shouldSkipConflictScanPath(relativePath) {
|
|
|
80
83
|
));
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
/** Build bounded process options, with extra headroom only for Forge's full suite. */
|
|
87
|
+
function getExecOptions(rootDir = process.cwd(), fullSuite = false) {
|
|
88
|
+
const options = {
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
cwd: rootDir,
|
|
91
|
+
timeout: fullSuite ? FULL_SUITE_TIMEOUT_MS : VALIDATION_COMMAND_TIMEOUT_MS,
|
|
92
|
+
};
|
|
93
|
+
if (fullSuite) options.maxBuffer = FULL_SUITE_MAX_BUFFER;
|
|
94
|
+
return options;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
const ERROR_PATTERNS = {
|
|
@@ -130,7 +140,7 @@ function parseNumber(match, index = 1, defaultValue = 0) {
|
|
|
130
140
|
*/
|
|
131
141
|
function getCheckStatus(check) {
|
|
132
142
|
if (!check) return null;
|
|
133
|
-
if (check.skipped) return 'SKIPPED';
|
|
143
|
+
if (check.skipped === true) return 'SKIPPED';
|
|
134
144
|
return check.success ? 'PASS' : 'FAIL';
|
|
135
145
|
}
|
|
136
146
|
|
|
@@ -290,6 +300,7 @@ async function runTypeCheck() {
|
|
|
290
300
|
return {
|
|
291
301
|
success: true,
|
|
292
302
|
skipped: true,
|
|
303
|
+
notConfigured: true,
|
|
293
304
|
duration: Date.now() - startTime,
|
|
294
305
|
message: 'TypeScript not configured (no tsconfig.json)',
|
|
295
306
|
};
|
|
@@ -343,17 +354,18 @@ async function runTypeCheck() {
|
|
|
343
354
|
* Run ESLint
|
|
344
355
|
* Executes eslint . to check code quality
|
|
345
356
|
*
|
|
357
|
+
* @param {typeof execFileSync} exec - Synchronous process runner
|
|
346
358
|
* @returns {Promise<{success: boolean, duration: number, warnings?: number, errors?: number, message?: string}>} Lint result
|
|
347
359
|
* @example
|
|
348
360
|
* const result = await runLint();
|
|
349
361
|
* console.log(`Warnings: ${result.warnings}, Errors: ${result.errors}`);
|
|
350
362
|
*/
|
|
351
|
-
async function runLint() {
|
|
363
|
+
async function runLint(exec = execFileSync) {
|
|
352
364
|
const startTime = Date.now();
|
|
353
365
|
|
|
354
366
|
try {
|
|
355
367
|
// Run eslint with no output (exit code determines success)
|
|
356
|
-
|
|
368
|
+
exec('eslint', ['.'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
|
|
357
369
|
|
|
358
370
|
return {
|
|
359
371
|
success: true,
|
|
@@ -556,7 +568,7 @@ async function runSecurityScan() { // NOSONAR S3776
|
|
|
556
568
|
|
|
557
569
|
/**
|
|
558
570
|
* Run all tests
|
|
559
|
-
*
|
|
571
|
+
* Uses Forge's full-suite runner when present, with raw Bun as an external-repository fallback
|
|
560
572
|
*
|
|
561
573
|
* @returns {Promise<{success: boolean, duration: number, passed: number, failed: number, total: number, message?: string}>} Test execution result
|
|
562
574
|
* @example
|
|
@@ -565,6 +577,22 @@ async function runSecurityScan() { // NOSONAR S3776
|
|
|
565
577
|
*/
|
|
566
578
|
function parseTestCounts(output) {
|
|
567
579
|
const text = String(output || '');
|
|
580
|
+
const aggregateMatches = [...text.matchAll(
|
|
581
|
+
/Full suite aggregate: status=(PASS|FAIL|INCOMPLETE) tests=(\d+) assertions=\d+ passed=(\d+) failed=(\d+) errors=(\d+) skipped=(\d+)/g,
|
|
582
|
+
)]; // NOSONAR S5852 - fixed tokens and bounded numeric fields
|
|
583
|
+
const aggregate = aggregateMatches.at(-1);
|
|
584
|
+
if (aggregate) {
|
|
585
|
+
const total = parseNumber(aggregate, 2);
|
|
586
|
+
return {
|
|
587
|
+
status: aggregate[1],
|
|
588
|
+
passed: parseNumber(aggregate, 3),
|
|
589
|
+
failed: parseNumber(aggregate, 4),
|
|
590
|
+
errors: parseNumber(aggregate, 5),
|
|
591
|
+
skipped: parseNumber(aggregate, 6),
|
|
592
|
+
total,
|
|
593
|
+
testsFound: total > 0,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
568
596
|
const passed = parseNumber(/(\d+) pass/.exec(text)); // NOSONAR S5852 - bounded \d+ pattern
|
|
569
597
|
const failed = parseNumber(/(\d+) fail/.exec(text)); // NOSONAR S5852 - bounded \d+ pattern
|
|
570
598
|
const skipped = parseNumber(/(\d+) skip/.exec(text)); // NOSONAR S5852 - bounded \d+ pattern
|
|
@@ -573,13 +601,31 @@ function parseTestCounts(output) {
|
|
|
573
601
|
return { passed, failed, skipped, total, testsFound: total > 0 };
|
|
574
602
|
}
|
|
575
603
|
|
|
576
|
-
|
|
604
|
+
function isForgeCheckout(rootDir) {
|
|
605
|
+
try {
|
|
606
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
|
|
607
|
+
return manifest.name === 'forge-workflow'
|
|
608
|
+
&& manifest.bin?.forge === 'bin/forge.js'
|
|
609
|
+
&& manifest.scripts?.['test:full:parallel'] === 'node scripts/test-full-suite.js'
|
|
610
|
+
&& fs.existsSync(path.join(rootDir, 'scripts', 'test-full-suite.js'));
|
|
611
|
+
} catch (error) {
|
|
612
|
+
if (error instanceof SyntaxError || error.code === 'ENOENT' || error.code === 'ENOTDIR') return false;
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async function runAllTests(exec = execFileSync, rootDir = process.cwd()) {
|
|
577
618
|
const startTime = Date.now();
|
|
619
|
+
const useFullSuite = isForgeCheckout(rootDir);
|
|
620
|
+
const command = useFullSuite ? 'node' : 'bun';
|
|
621
|
+
const args = useFullSuite ? ['scripts/test-full-suite.js'] : ['test', '--timeout', '30000'];
|
|
622
|
+
const execOptions = getExecOptions(rootDir, useFullSuite);
|
|
578
623
|
|
|
579
624
|
try {
|
|
580
|
-
const result = exec(
|
|
625
|
+
const result = exec(command, args, execOptions); // NOSONAR S4036 - fixed CLI commands, no user input, developer tool context
|
|
581
626
|
|
|
582
|
-
const { passed, failed, skipped, total, testsFound } = parseTestCounts(result);
|
|
627
|
+
const { status, passed, failed, errors = 0, skipped, total, testsFound } = parseTestCounts(result);
|
|
628
|
+
const success = failed === 0 && errors === 0 && (status === undefined || status === 'PASS');
|
|
583
629
|
|
|
584
630
|
// B2 (N1): a bun run that executes 0 tests must NEVER report PASS — that
|
|
585
631
|
// manufactures false confidence. Surface it as an explicit SKIP instead.
|
|
@@ -588,6 +634,7 @@ async function runAllTests(exec = execFileSync) {
|
|
|
588
634
|
success: true,
|
|
589
635
|
skipped: true,
|
|
590
636
|
testsFound: false,
|
|
637
|
+
fullSuite: useFullSuite,
|
|
591
638
|
duration: Date.now() - startTime,
|
|
592
639
|
passed,
|
|
593
640
|
failed,
|
|
@@ -597,27 +644,29 @@ async function runAllTests(exec = execFileSync) {
|
|
|
597
644
|
}
|
|
598
645
|
|
|
599
646
|
return {
|
|
600
|
-
success
|
|
647
|
+
success,
|
|
601
648
|
testsFound: true,
|
|
649
|
+
fullSuite: useFullSuite,
|
|
602
650
|
duration: Date.now() - startTime,
|
|
603
651
|
passed,
|
|
604
652
|
failed,
|
|
605
653
|
skipped,
|
|
606
654
|
total,
|
|
607
|
-
message:
|
|
655
|
+
message: success
|
|
608
656
|
? `All ${total} tests passed`
|
|
609
|
-
: `${failed}/${total} tests failed`,
|
|
657
|
+
: `${failed + errors}/${total} tests failed`,
|
|
610
658
|
};
|
|
611
659
|
} catch (error) {
|
|
612
660
|
// Check for timeout
|
|
613
|
-
if (error.killed && error.signal === 'SIGTERM') {
|
|
661
|
+
if (error.code === 'ETIMEDOUT' || (error.killed && error.signal === 'SIGTERM')) {
|
|
614
662
|
return {
|
|
615
663
|
success: false,
|
|
664
|
+
fullSuite: useFullSuite,
|
|
616
665
|
duration: Date.now() - startTime,
|
|
617
666
|
passed: 0,
|
|
618
667
|
failed: 0,
|
|
619
668
|
total: 0,
|
|
620
|
-
message: validationTimeoutMessage('Test execution'),
|
|
669
|
+
message: validationTimeoutMessage('Test execution', execOptions.timeout),
|
|
621
670
|
};
|
|
622
671
|
}
|
|
623
672
|
|
|
@@ -627,11 +676,12 @@ async function runAllTests(exec = execFileSync) {
|
|
|
627
676
|
success: true,
|
|
628
677
|
skipped: true,
|
|
629
678
|
testsFound: false,
|
|
679
|
+
fullSuite: useFullSuite,
|
|
630
680
|
duration: Date.now() - startTime,
|
|
631
681
|
passed: 0,
|
|
632
682
|
failed: 0,
|
|
633
683
|
total: 0,
|
|
634
|
-
message: 'Tests skipped:
|
|
684
|
+
message: 'Tests skipped: test runner not found. Install Node.js and Bun to run tests.',
|
|
635
685
|
};
|
|
636
686
|
}
|
|
637
687
|
|
|
@@ -641,12 +691,13 @@ async function runAllTests(exec = execFileSync) {
|
|
|
641
691
|
const passed = counts.passed;
|
|
642
692
|
// bun exited non-zero: if the output shows no explicit failure count, treat
|
|
643
693
|
// it as at least one failure rather than swallowing the non-zero exit.
|
|
644
|
-
const failed = counts.failed > 0 ? counts.failed : Math.max(1, counts.
|
|
694
|
+
const failed = counts.failed > 0 ? counts.failed : Math.max(1, counts.errors || 0);
|
|
645
695
|
const total = counts.total > 0 ? counts.total : passed + failed + counts.skipped;
|
|
646
696
|
|
|
647
697
|
return {
|
|
648
698
|
success: false,
|
|
649
699
|
testsFound: counts.testsFound,
|
|
700
|
+
fullSuite: useFullSuite,
|
|
650
701
|
duration: Date.now() - startTime,
|
|
651
702
|
passed,
|
|
652
703
|
failed,
|
|
@@ -680,6 +731,9 @@ async function runAllTests(exec = execFileSync) {
|
|
|
680
731
|
*/
|
|
681
732
|
async function executeValidate(options = {}) { // NOSONAR S3776
|
|
682
733
|
const { skip = [], continueOnError = true, rootDir = process.cwd() } = options || {};
|
|
734
|
+
const receiptApi = options?.validationReceipt || validationReceipt;
|
|
735
|
+
const validationSnapshot = receiptApi.beginValidation(rootDir);
|
|
736
|
+
const receiptSnapshot = skip.length === 0 ? validationSnapshot : null;
|
|
683
737
|
|
|
684
738
|
const checks = {};
|
|
685
739
|
const failedChecks = [];
|
|
@@ -753,8 +807,8 @@ async function executeValidate(options = {}) { // NOSONAR S3776
|
|
|
753
807
|
// 4. Tests
|
|
754
808
|
if (!skip.includes(CHECK_TYPES.TESTS)) {
|
|
755
809
|
try {
|
|
756
|
-
checks.tests = await runAllTests();
|
|
757
|
-
if (!checks.tests.success &&
|
|
810
|
+
checks.tests = await runAllTests(execFileSync, rootDir);
|
|
811
|
+
if (!checks.tests.success && checks.tests.skipped !== true) {
|
|
758
812
|
failedChecks.push(CHECK_TYPES.TESTS);
|
|
759
813
|
if (!continueOnError) {
|
|
760
814
|
return buildResult(checks, failedChecks, errors, startTime);
|
|
@@ -766,7 +820,9 @@ async function executeValidate(options = {}) { // NOSONAR S3776
|
|
|
766
820
|
}
|
|
767
821
|
}
|
|
768
822
|
|
|
769
|
-
|
|
823
|
+
const result = buildResult(checks, failedChecks, errors, startTime);
|
|
824
|
+
result.validationReceipt = receiptApi.completeValidation(rootDir, receiptSnapshot, result);
|
|
825
|
+
return result;
|
|
770
826
|
}
|
|
771
827
|
|
|
772
828
|
/**
|
package/lib/commands/worktree.js
CHANGED
|
@@ -109,20 +109,106 @@ function isLinkPermissionError(error) {
|
|
|
109
109
|
// inspect, and `yarn check` was removed in Berry, so there is nothing to verify.
|
|
110
110
|
const PROBE_LESS_MANAGERS = new Set(['yarn']);
|
|
111
111
|
|
|
112
|
+
function samePath(left, right) {
|
|
113
|
+
return path.relative(left, right) === '';
|
|
114
|
+
}
|
|
115
|
+
|
|
112
116
|
/**
|
|
113
|
-
* Direct dependencies the
|
|
114
|
-
* they may legitimately be absent.
|
|
115
|
-
*
|
|
117
|
+
* Direct dependencies the root and its workspaces declare. Optional/peer deps
|
|
118
|
+
* are excluded — they may legitimately be absent. Workspace discovery fails
|
|
119
|
+
* closed so an incomplete manifest set cannot prove an install complete.
|
|
116
120
|
* @param {string} worktreePath - Absolute path to the worktree
|
|
117
121
|
* @param {object} fsApi - fs module (for DI)
|
|
118
|
-
* @returns {string
|
|
122
|
+
* @returns {Array<{dir: string, name: string, workspaceDir: string|null}>} Declaring directory and package name
|
|
119
123
|
*/
|
|
120
124
|
function declaredDependencies(worktreePath, fsApi) {
|
|
125
|
+
let rootPkg;
|
|
126
|
+
try {
|
|
127
|
+
rootPkg = JSON.parse(fsApi.readFileSync(path.join(worktreePath, 'package.json'), 'utf8'));
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw new Error(`Cannot verify dependencies: unreadable worktree package.json: ${error.message}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let workspacePatterns = [];
|
|
133
|
+
if (rootPkg.workspaces != null) {
|
|
134
|
+
workspacePatterns = Array.isArray(rootPkg.workspaces)
|
|
135
|
+
? rootPkg.workspaces
|
|
136
|
+
: rootPkg.workspaces.packages;
|
|
137
|
+
if (!Array.isArray(workspacePatterns)) {
|
|
138
|
+
throw new Error('Cannot verify dependencies: workspaces must be an array or an object with a packages array');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (workspacePatterns.length && typeof fsApi.globSync !== 'function') {
|
|
143
|
+
throw new Error('Cannot verify dependencies: this runtime does not provide workspace glob support');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const manifestPaths = new Set();
|
|
147
|
+
for (const rawPattern of workspacePatterns) {
|
|
148
|
+
if (typeof rawPattern !== 'string' || !rawPattern.trim()) {
|
|
149
|
+
throw new Error(`Cannot verify dependencies: invalid workspace pattern ${JSON.stringify(rawPattern)}`);
|
|
150
|
+
}
|
|
151
|
+
const exclude = rawPattern.startsWith('!');
|
|
152
|
+
const pattern = (exclude ? rawPattern.slice(1) : rawPattern).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
153
|
+
const resolvedPattern = resolveRepoRelativePath(worktreePath, pattern);
|
|
154
|
+
if (!pattern || pattern.includes('\0') || path.isAbsolute(pattern) || !resolvedPattern.insideRoot) {
|
|
155
|
+
throw new Error(`Cannot verify dependencies: workspace pattern resolves outside the worktree: ${rawPattern}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let matches;
|
|
159
|
+
try {
|
|
160
|
+
matches = fsApi.globSync(`${pattern}/package.json`, {
|
|
161
|
+
cwd: worktreePath,
|
|
162
|
+
exclude: candidate => String(candidate).split(/[\\/]/).includes('node_modules'),
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error(`Cannot verify dependencies: workspace glob failed for ${rawPattern}: ${error.message}`);
|
|
166
|
+
}
|
|
167
|
+
for (const match of matches) {
|
|
168
|
+
const resolved = resolveRepoRelativePath(worktreePath, String(match));
|
|
169
|
+
if (!resolved.insideRoot) {
|
|
170
|
+
throw new Error(`Cannot verify dependencies: workspace glob resolved outside the worktree: ${match}`);
|
|
171
|
+
}
|
|
172
|
+
const existing = [...manifestPaths].find(manifestPath => samePath(manifestPath, resolved.target));
|
|
173
|
+
if (exclude && existing) manifestPaths.delete(existing);
|
|
174
|
+
else if (!exclude && !existing) manifestPaths.add(resolved.target);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const manifests = [{ dir: worktreePath, pkg: rootPkg }];
|
|
179
|
+
for (const manifestPath of manifestPaths) {
|
|
180
|
+
try {
|
|
181
|
+
manifests.push({
|
|
182
|
+
dir: path.dirname(manifestPath),
|
|
183
|
+
pkg: JSON.parse(fsApi.readFileSync(manifestPath, 'utf8')),
|
|
184
|
+
});
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error && ['ENOENT', 'ENOTDIR'].includes(error.code)) continue;
|
|
187
|
+
throw new Error(`Cannot verify dependencies: unreadable workspace manifest ${manifestPath}: ${error.message}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const workspaceDirs = new Map();
|
|
192
|
+
for (const { dir, pkg } of manifests.slice(1)) {
|
|
193
|
+
if (!pkg.name) continue;
|
|
194
|
+
if (workspaceDirs.has(pkg.name) && !samePath(workspaceDirs.get(pkg.name), dir)) {
|
|
195
|
+
throw new Error(`Cannot verify dependencies: duplicate workspace package name ${pkg.name}`);
|
|
196
|
+
}
|
|
197
|
+
workspaceDirs.set(pkg.name, dir);
|
|
198
|
+
}
|
|
199
|
+
return manifests.flatMap(({ dir, pkg }) => [
|
|
200
|
+
...Object.keys(pkg.dependencies || {}),
|
|
201
|
+
...Object.keys(pkg.devDependencies || {}),
|
|
202
|
+
].map(name => ({ dir, name, workspaceDir: workspaceDirs.get(name) || null })));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function dependencyCandidatePresent(candidate, workspaceDir, fsApi) {
|
|
206
|
+
if (!fsApi.existsSync(path.join(candidate, 'package.json'))) return false;
|
|
207
|
+
if (!workspaceDir) return true;
|
|
121
208
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return [];
|
|
209
|
+
return path.relative(fsApi.realpathSync(workspaceDir), fsApi.realpathSync(candidate)) === '';
|
|
210
|
+
} catch (_err) { /* intentional: an unresolvable workspace link is not installed */ // NOSONAR S2486
|
|
211
|
+
return false;
|
|
126
212
|
}
|
|
127
213
|
}
|
|
128
214
|
|
|
@@ -138,11 +224,33 @@ function declaredDependencies(worktreePath, fsApi) {
|
|
|
138
224
|
*
|
|
139
225
|
* @param {string} worktreePath - Absolute path to the worktree
|
|
140
226
|
* @param {object} fsApi - fs module (for DI)
|
|
227
|
+
* @param {string|null} pkgManager - Detected package manager
|
|
141
228
|
* @returns {boolean} true when nothing is missing
|
|
142
229
|
*/
|
|
143
|
-
function dependenciesPresent(worktreePath, fsApi) {
|
|
230
|
+
function dependenciesPresent(worktreePath, fsApi, pkgManager) {
|
|
231
|
+
if (!pkgManager) return true;
|
|
144
232
|
const deps = declaredDependencies(worktreePath, fsApi);
|
|
145
|
-
return deps.every((
|
|
233
|
+
return deps.every(({ dir, name, workspaceDir }) => {
|
|
234
|
+
const localPackage = path.join(dir, 'node_modules', name);
|
|
235
|
+
if (fsApi.existsSync(localPackage)) {
|
|
236
|
+
return dependencyCandidatePresent(localPackage, workspaceDir, fsApi);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const rootPackage = path.join(worktreePath, 'node_modules', name);
|
|
240
|
+
if (dir === worktreePath || (pkgManager === 'bun' && !workspaceDir)) return false;
|
|
241
|
+
return dependencyCandidatePresent(rootPackage, workspaceDir, fsApi);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function detachModulesLink(modulesPath, fsApi) {
|
|
246
|
+
try {
|
|
247
|
+
if (!fsApi.lstatSync(modulesPath).isSymbolicLink()) return false;
|
|
248
|
+
fsApi.unlinkSync(modulesPath);
|
|
249
|
+
return true;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error && error.code === 'ENOENT') return false;
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
146
254
|
}
|
|
147
255
|
|
|
148
256
|
const STDERR_DETAIL_LIMIT = 400;
|
|
@@ -199,21 +307,21 @@ function spawnInstall(pkgManager, args, worktreePath, spawnFn) {
|
|
|
199
307
|
* @param {string} projectRoot - Absolute path to the project root
|
|
200
308
|
* @param {Function} spawnFn - spawnSync-compatible function
|
|
201
309
|
* @param {object} fsApi - fs module (for DI)
|
|
310
|
+
* @param {string|null} [pkgManager] - Detected package manager
|
|
202
311
|
* @returns {{ linked: boolean, installed: boolean, healed: boolean }}
|
|
203
312
|
* @throws {Error} when the install fails, or the tree is still broken after --force.
|
|
204
313
|
*/
|
|
205
|
-
function runInstall(worktreePath, projectRoot, spawnFn, fsApi) {
|
|
206
|
-
const pkgManager = detectPackageManager(projectRoot, fsApi);
|
|
314
|
+
function runInstall(worktreePath, projectRoot, spawnFn, fsApi, pkgManager = detectPackageManager(projectRoot, fsApi)) {
|
|
207
315
|
if (!pkgManager) return { linked: false, installed: false, healed: false };
|
|
208
316
|
|
|
209
317
|
spawnInstall(pkgManager, ['install'], worktreePath, spawnFn);
|
|
210
318
|
|
|
211
|
-
if (PROBE_LESS_MANAGERS.has(pkgManager) || dependenciesPresent(worktreePath, fsApi)) {
|
|
319
|
+
if (PROBE_LESS_MANAGERS.has(pkgManager) || dependenciesPresent(worktreePath, fsApi, pkgManager)) {
|
|
212
320
|
return { linked: false, installed: true, healed: false };
|
|
213
321
|
}
|
|
214
322
|
|
|
215
323
|
spawnInstall(pkgManager, ['install', '--force'], worktreePath, spawnFn);
|
|
216
|
-
if (!dependenciesPresent(worktreePath, fsApi)) {
|
|
324
|
+
if (!dependenciesPresent(worktreePath, fsApi, pkgManager)) {
|
|
217
325
|
throw new Error(`Dependency install could not be verified: packages are still missing in ${worktreePath} after '${pkgManager} install --force'. Run it manually there and inspect the output.`);
|
|
218
326
|
}
|
|
219
327
|
return { linked: false, installed: true, healed: true };
|
|
@@ -237,24 +345,38 @@ function runInstall(worktreePath, projectRoot, spawnFn, fsApi) {
|
|
|
237
345
|
function setupWorktreeDeps(worktreePath, projectRoot, { spawnFn, fsApi, platform }) {
|
|
238
346
|
const srcModules = path.join(projectRoot, 'node_modules');
|
|
239
347
|
const destModules = path.join(worktreePath, 'node_modules');
|
|
348
|
+
const pkgManager = detectPackageManager(projectRoot, fsApi);
|
|
349
|
+
|
|
350
|
+
// Keep the zero-install fast path only when root and workspace dependencies
|
|
351
|
+
// are complete; Bun's isolated linker keeps workspace packages below each workspace.
|
|
352
|
+
if (fsApi.existsSync(destModules)) {
|
|
353
|
+
if (dependenciesPresent(worktreePath, fsApi, pkgManager)) return { linked: false, installed: false, healed: false };
|
|
354
|
+
detachModulesLink(destModules, fsApi);
|
|
355
|
+
return runInstall(worktreePath, projectRoot, spawnFn, fsApi, pkgManager);
|
|
356
|
+
}
|
|
240
357
|
|
|
241
|
-
//
|
|
242
|
-
|
|
358
|
+
// existsSync follows links, so remove a dangling directory entry before
|
|
359
|
+
// either replacing it with the shared install or installing in its place.
|
|
360
|
+
detachModulesLink(destModules, fsApi);
|
|
243
361
|
|
|
244
362
|
// Fast path: link to the shared install when the main repo has one.
|
|
245
363
|
if (fsApi.existsSync(srcModules)) {
|
|
246
364
|
const linkType = platform === 'win32' ? 'junction' : 'dir';
|
|
247
365
|
try {
|
|
248
366
|
fsApi.symlinkSync(srcModules, destModules, linkType);
|
|
249
|
-
return { linked: true, installed: false, healed: false };
|
|
250
367
|
} catch (error) {
|
|
251
368
|
// Degrade to a real install only when the link was refused for lack of
|
|
252
369
|
// privilege/support; any other failure is surfaced to the caller.
|
|
253
370
|
if (!isLinkPermissionError(error)) throw error;
|
|
371
|
+
return runInstall(worktreePath, projectRoot, spawnFn, fsApi, pkgManager);
|
|
372
|
+
}
|
|
373
|
+
if (dependenciesPresent(worktreePath, fsApi, pkgManager)) {
|
|
374
|
+
return { linked: true, installed: false, healed: false };
|
|
254
375
|
}
|
|
376
|
+
fsApi.unlinkSync(destModules);
|
|
255
377
|
}
|
|
256
378
|
|
|
257
|
-
return runInstall(worktreePath, projectRoot, spawnFn, fsApi);
|
|
379
|
+
return runInstall(worktreePath, projectRoot, spawnFn, fsApi, pkgManager);
|
|
258
380
|
}
|
|
259
381
|
|
|
260
382
|
function readExistingWorktreeLinkage(driver, worktreePath, reuse) {
|
|
@@ -504,12 +626,26 @@ async function handleCreate(slug, flags, projectRoot, opts) {
|
|
|
504
626
|
if (linkage.conflict) {
|
|
505
627
|
return { success: false, reused: true, error: linkage.reason, worktreePath, linkage };
|
|
506
628
|
}
|
|
629
|
+
let deps;
|
|
630
|
+
try {
|
|
631
|
+
deps = setupWorktreeDeps(worktreePath, projectRoot, {
|
|
632
|
+
spawnFn: runSpawn,
|
|
633
|
+
fsApi,
|
|
634
|
+
platform: opts._platform || process.platform,
|
|
635
|
+
});
|
|
636
|
+
} catch (error) {
|
|
637
|
+
return { success: false, reused: true, error: error.message, worktreePath, linkage };
|
|
638
|
+
}
|
|
507
639
|
const backing = await autoFileBackingIssue({ projectRoot, worktreePath, branch: existingBranch, issueId, opts });
|
|
640
|
+
const healNote = deps.healed ? ' Healed dependencies (store was stale): reinstalled with --force.' : '';
|
|
508
641
|
return {
|
|
509
642
|
success: true,
|
|
510
643
|
reused: true,
|
|
511
|
-
message: `Worktree already exists at ${worktreePath}`,
|
|
644
|
+
message: `Worktree already exists at ${worktreePath}.${healNote}`,
|
|
512
645
|
worktreePath,
|
|
646
|
+
depsLinked: deps.linked,
|
|
647
|
+
depsInstalled: deps.installed,
|
|
648
|
+
depsHealed: deps.healed,
|
|
513
649
|
linkage,
|
|
514
650
|
backing,
|
|
515
651
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Forge 0.1.0-beta.5 compatibility corpus v1
|
|
2
|
+
|
|
3
|
+
This immutable corpus is derived from release commit
|
|
4
|
+
`ebeb4e5b31fc2dacdf23c5936c02fb2656990f49`. Contract snapshots retain only
|
|
5
|
+
public package, binary, command, alias, and workflow facts. State rows are
|
|
6
|
+
synthetic and contain no user, device, prompt, tool-log, or repository data.
|
|
7
|
+
|
|
8
|
+
`manifest.json` hashes every corpus member. Change requires a new corpus
|
|
9
|
+
version; never rewrite v1 after it becomes release evidence.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"commands": ["adapter", "add", "audit", "blocked", "claim", "claims", "clean", "close", "comment", "control", "create", "dev", "doc-gate", "doctor", "explain", "export", "gate", "hooks", "inbox", "init", "insights", "issue", "issues", "lint", "list", "memory", "merge", "migrate", "new", "options", "orient", "orphans", "patch", "plan", "pr", "preflight", "prime", "push", "ready", "recall", "recap", "recommend", "release", "remember", "role", "serve", "setup", "shepherd", "ship", "show", "skill", "stage", "stale", "status", "sync", "team", "test", "update", "upgrade", "validate", "worktree"],
|
|
3
|
+
"aliases": {
|
|
4
|
+
"blocked": { "canonical": "issue blocked", "visible": false },
|
|
5
|
+
"claim": { "canonical": "issue claim", "visible": false },
|
|
6
|
+
"claims": { "canonical": "issue claims", "visible": false },
|
|
7
|
+
"close": { "canonical": "issue close", "visible": false },
|
|
8
|
+
"create": { "canonical": "issue create", "visible": false },
|
|
9
|
+
"doc-gate": { "canonical": "gate doc", "visible": false },
|
|
10
|
+
"insights": { "canonical": "memory insights", "visible": true },
|
|
11
|
+
"issues": { "canonical": "issue list", "visible": false },
|
|
12
|
+
"lint": { "canonical": "issue lint", "visible": false },
|
|
13
|
+
"list": { "canonical": "issue list", "visible": false },
|
|
14
|
+
"merge": { "canonical": "pr merge", "visible": false },
|
|
15
|
+
"orphans": { "canonical": "issue orphans", "visible": false },
|
|
16
|
+
"preflight": { "canonical": "pr preflight", "visible": true },
|
|
17
|
+
"ready": { "canonical": "issue ready", "visible": false },
|
|
18
|
+
"recall": { "canonical": "memory recall", "visible": true },
|
|
19
|
+
"remember": { "canonical": "memory add", "visible": true },
|
|
20
|
+
"shepherd": { "canonical": "pr shepherd", "visible": false },
|
|
21
|
+
"ship": { "canonical": "pr ship", "visible": true },
|
|
22
|
+
"show": { "canonical": "issue show", "visible": false },
|
|
23
|
+
"stale": { "canonical": "issue stale", "visible": false },
|
|
24
|
+
"update": { "canonical": "issue update", "visible": false }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "forge-workflow",
|
|
3
|
+
"version": "0.1.0-beta.5",
|
|
4
|
+
"release_commit": "ebeb4e5b31fc2dacdf23c5936c02fb2656990f49",
|
|
5
|
+
"binaries": {
|
|
6
|
+
"forge": "bin/forge.js",
|
|
7
|
+
"forge-workflow": "bin/forge.js",
|
|
8
|
+
"forge-preflight": "bin/forge-preflight.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22.16.0"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"critical": ["plan", "dev", "validate", "ship", "review", "verify"],
|
|
3
|
+
"standard": ["plan", "dev", "validate", "ship", "review"],
|
|
4
|
+
"refactor": ["plan", "dev", "validate", "ship"],
|
|
5
|
+
"simple": ["dev", "validate", "ship"],
|
|
6
|
+
"hotfix": ["dev", "validate", "ship"],
|
|
7
|
+
"docs": ["verify", "ship"]
|
|
8
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "forge.beta5-corpus.v1",
|
|
3
|
+
"corpus_version": 1,
|
|
4
|
+
"release_version": "0.1.0-beta.5",
|
|
5
|
+
"release_commit": "ebeb4e5b31fc2dacdf23c5936c02fb2656990f49",
|
|
6
|
+
"source_blobs": {
|
|
7
|
+
"package.json": "6f3f112341cd50e0e76fad615beb007b9e6dff2c",
|
|
8
|
+
"lib/commands/_manifest.js": "62595c38b66b139244ca1f54cb034f67d849247d",
|
|
9
|
+
"lib/commands/_aliases.js": "2b16d61d1cdb9ef3e594b5a5093206d05844b460",
|
|
10
|
+
"lib/workflow/stages.js": "0b7ae32bddcbb9bd6dd52a07ca22e43364621918"
|
|
11
|
+
},
|
|
12
|
+
"hash_algorithm": "sha256",
|
|
13
|
+
"content_hash": "c6978eed757c6255f2d25ea6d49ada44e5b3da91830d348016267981d13afd18",
|
|
14
|
+
"files": [
|
|
15
|
+
{ "path": "README.md", "sha256": "3cfcc78856e397f2cfd3fee9fa2fffb630e27ea3e76ee8d8b5d9a77cb366a709" },
|
|
16
|
+
{ "path": "contract/command-contract.json", "sha256": "8b4f885091fa8afe72b7b5a5a8039869c871f4fb1dc3344728b2c4322f491a84" },
|
|
17
|
+
{ "path": "contract/package-contract.json", "sha256": "a65280ede51d62c1913140c83b40e6c3312c991c7a173e7a9ad52d3d5ea0fd47" },
|
|
18
|
+
{ "path": "contract/workflow-stage-matrix.json", "sha256": "2aadafdb5f5c1817c8289dd6134d034e3ccc6579438935520bd3c6b90401e5bf" },
|
|
19
|
+
{ "path": "state/comments.jsonl", "sha256": "ae4f694f207a11a22b6798943cc6d07487d1dc2be83a78ceadad947a3ff40113" },
|
|
20
|
+
{ "path": "state/config.yaml", "sha256": "0b877d9180cf14951657254b50cd771e131970e92582a7c0337f36418a35e1c0" },
|
|
21
|
+
{ "path": "state/dependencies.jsonl", "sha256": "cb43cc053d6dd885254a26f6d6b024883f770eb68f50b715cc2f0f05e2cd31e5" },
|
|
22
|
+
{ "path": "state/issues.jsonl", "sha256": "425567c5c7b70fd6bbdf9ead3aff78c09a77ebe7b78d3c856c045743b8cb924e" },
|
|
23
|
+
{ "path": "state/kernel.sql", "sha256": "4208aabcadc6cec3234ebb5abd4cbf32cc74805433d04a05801e0b6354b53d27" }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"synthetic-comment-1","issue_id":"synthetic-issue-1","body":"Synthetic comment","actor":"fixture"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"issue_id":"synthetic-issue-1","blocks_issue_id":"synthetic-issue-2"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
PRAGMA user_version = 1;
|
|
2
|
+
CREATE TABLE kernel_migrations (id TEXT PRIMARY KEY, applied_at TEXT NOT NULL);
|
|
3
|
+
CREATE TABLE kernel_issues (id TEXT PRIMARY KEY, title TEXT NOT NULL, status TEXT NOT NULL, entity_revision INTEGER NOT NULL);
|
|
4
|
+
CREATE TABLE kernel_comments (id TEXT PRIMARY KEY, issue_id TEXT NOT NULL, body TEXT NOT NULL, actor TEXT NOT NULL);
|
|
5
|
+
CREATE TABLE kernel_dependencies (id TEXT PRIMARY KEY, issue_id TEXT NOT NULL, blocks_issue_id TEXT NOT NULL);
|
|
6
|
+
CREATE TABLE kernel_claims (id TEXT PRIMARY KEY, issue_id TEXT NOT NULL, actor TEXT NOT NULL, state TEXT NOT NULL);
|
|
7
|
+
CREATE TABLE kernel_worktrees (id TEXT PRIMARY KEY, path TEXT NOT NULL, branch TEXT NOT NULL, state TEXT NOT NULL);
|
|
8
|
+
CREATE TABLE kernel_stage_runs (id TEXT PRIMARY KEY, issue_id TEXT NOT NULL, stage TEXT NOT NULL, status TEXT NOT NULL);
|
|
9
|
+
CREATE TABLE kernel_projections (id TEXT PRIMARY KEY, target TEXT NOT NULL, entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, status TEXT NOT NULL);
|
|
10
|
+
CREATE TABLE kernel_events (id TEXT PRIMARY KEY, entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, event_type TEXT NOT NULL);
|
|
11
|
+
INSERT INTO kernel_migrations VALUES ('001_initial_kernel_schema', '2026-08-09T00:00:00.000Z');
|
|
12
|
+
INSERT INTO kernel_issues VALUES ('synthetic-issue-1', 'Synthetic issue', 'open', 2); -- NOSONAR S1192: repeated synthetic foreign key preserves fixture readability.
|
|
13
|
+
INSERT INTO kernel_issues VALUES ('synthetic-issue-2', 'Synthetic dependency', 'done', 1);
|
|
14
|
+
INSERT INTO kernel_comments VALUES ('synthetic-comment-1', 'synthetic-issue-1', 'Synthetic comment', 'fixture');
|
|
15
|
+
INSERT INTO kernel_dependencies VALUES ('synthetic-dependency-1', 'synthetic-issue-1', 'synthetic-issue-2');
|
|
16
|
+
INSERT INTO kernel_claims VALUES ('synthetic-claim-1', 'synthetic-issue-1', 'fixture', 'active');
|
|
17
|
+
INSERT INTO kernel_worktrees VALUES ('synthetic-worktree-1', '/synthetic/worktree', 'fixture/branch', 'active');
|
|
18
|
+
INSERT INTO kernel_stage_runs VALUES ('synthetic-run-1', 'synthetic-issue-1', 'dev', 'done');
|
|
19
|
+
INSERT INTO kernel_projections VALUES ('synthetic-projection-1', 'jsonl', 'issue', 'synthetic-issue-1', 'delivered');
|
|
20
|
+
INSERT INTO kernel_events VALUES ('synthetic-event-1', 'issue', 'synthetic-issue-1', 'issue.updated');
|