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
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createProbeResult } = require('./model');
|
|
4
|
+
|
|
5
|
+
const PROBE_REVISION = 'forge.harness-capability-probe.v1';
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 1500;
|
|
7
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 16 * 1024;
|
|
8
|
+
const MAX_TIMEOUT_MS = 30 * 1000;
|
|
9
|
+
const MAX_OUTPUT_BYTES = 64 * 1024;
|
|
10
|
+
const EXECUTABLE_IDENTITY_PATTERN = /^[0-9a-f]{64}$/;
|
|
11
|
+
const SECRET_PATTERNS = Object.freeze([
|
|
12
|
+
/gh[pousr]_[a-z0-9]{20,}/i,
|
|
13
|
+
/sk-(?:live-|test-)?[a-z0-9]{16,}/i,
|
|
14
|
+
/(?:api[_ -]?key|authorization|credential|password|private[_ -]?key|secret|token)\s*[:=]\s*\S{8,}/i,
|
|
15
|
+
]);
|
|
16
|
+
const PRIVATE_PATH_PATTERN = /(?:[a-z]:\\Users\\[^\\\s]+|\/(?:Users|home)\/[^/\s]+\/)/i;
|
|
17
|
+
|
|
18
|
+
const HARNESS_IDS = Object.freeze(['claude', 'codex', 'cursor', 'hermes']);
|
|
19
|
+
const CAPABILITY_IDS = Object.freeze([
|
|
20
|
+
'native_streaming_monitor',
|
|
21
|
+
'bounded_background_tasks',
|
|
22
|
+
'wake_resume',
|
|
23
|
+
'cancellation',
|
|
24
|
+
'session_scoped_cleanup',
|
|
25
|
+
'initiating_agent_delivery',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function capability(id, probeId, args, requiredTokens) {
|
|
29
|
+
if (args.length === 0 || args.at(-1) !== '--help') {
|
|
30
|
+
throw new TypeError(`Capability probe ${probeId} must use a side-effect-free help command`);
|
|
31
|
+
}
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
id,
|
|
34
|
+
probeId,
|
|
35
|
+
args: Object.freeze(args),
|
|
36
|
+
requiredTokens: Object.freeze(requiredTokens),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function harnessSpec(command, versionPattern, capabilities) {
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
command,
|
|
43
|
+
versionArgs: Object.freeze(['--version']),
|
|
44
|
+
versionPattern,
|
|
45
|
+
capabilities: Object.freeze(capabilities),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const HARNESS_PROBE_SPECS = Object.freeze({
|
|
50
|
+
claude: harnessSpec('claude', /^(?:claude(?:\s+code)?\s+)?v?(\d+\.\d+\.\d+(?:[-+][0-9a-z.-]+)?)$/i, [
|
|
51
|
+
capability('native_streaming_monitor', 'claude.stream-json-help.v1', ['--help'], ['--output-format', 'stream-json']),
|
|
52
|
+
capability('bounded_background_tasks', 'claude.remote-help.v1', ['--help'], ['--remote']),
|
|
53
|
+
capability('wake_resume', 'claude.resume-help.v1', ['--help'], ['--resume']),
|
|
54
|
+
capability('cancellation', 'claude.cancel-help.v1', ['--help'], ['--cancel']),
|
|
55
|
+
capability('session_scoped_cleanup', 'claude.session-cleanup-help.v1', ['--help'], ['session', 'delete']),
|
|
56
|
+
capability('initiating_agent_delivery', 'claude.stream-delivery-help.v1', ['--help'], ['--output-format', 'stream-json']),
|
|
57
|
+
]),
|
|
58
|
+
codex: harnessSpec('codex', /^(?:codex(?:-cli)?\s+)?v?(\d+\.\d+\.\d+(?:[-+][0-9a-z.-]+)?)$/i, [
|
|
59
|
+
capability('native_streaming_monitor', 'codex.json-help.v1', ['--help'], ['--json', 'output-schema']),
|
|
60
|
+
capability('bounded_background_tasks', 'codex.cloud-help.v1', ['cloud', '--help'], ['task']),
|
|
61
|
+
capability('wake_resume', 'codex.resume-help.v1', ['resume', '--help'], ['resume']),
|
|
62
|
+
capability('cancellation', 'codex.cloud-cancel-help.v1', ['cloud', '--help'], ['cancel']),
|
|
63
|
+
capability('session_scoped_cleanup', 'codex.cloud-cleanup-help.v1', ['cloud', '--help'], ['delete']),
|
|
64
|
+
capability('initiating_agent_delivery', 'codex.delivery-help.v1', ['--help'], ['event', 'initiating', 'agent']),
|
|
65
|
+
]),
|
|
66
|
+
cursor: harnessSpec('cursor-agent', /^(?:cursor(?:-agent)?\s+)?v?(\d+\.\d+\.\d+(?:[-+][0-9a-z.-]+)?)$/i, [
|
|
67
|
+
capability('native_streaming_monitor', 'cursor.json-help.v1', ['--help'], ['--json']),
|
|
68
|
+
capability('bounded_background_tasks', 'cursor.background-help.v1', ['--help'], ['background']),
|
|
69
|
+
capability('wake_resume', 'cursor.resume-help.v1', ['resume', '--help'], ['resume']),
|
|
70
|
+
capability('cancellation', 'cursor.cancel-help.v1', ['cancel', '--help'], ['cancel']),
|
|
71
|
+
capability('session_scoped_cleanup', 'cursor.session-cleanup-help.v1', ['sessions', '--help'], ['sessions', 'delete']),
|
|
72
|
+
capability('initiating_agent_delivery', 'cursor.follow-up-help.v1', ['--help'], ['follow-up', 'initiating', 'agent']),
|
|
73
|
+
]),
|
|
74
|
+
hermes: harnessSpec('hermes', /^(?:hermes(?:-agent)?\s+)?v?(\d+\.\d+\.\d+(?:[-+][0-9a-z.-]+)?)$/i, [
|
|
75
|
+
capability('native_streaming_monitor', 'hermes.monitor-help.v1', ['monitor', '--help'], ['monitor', 'stream']),
|
|
76
|
+
capability('bounded_background_tasks', 'hermes.cron-help.v1', ['cron', '--help'], ['cron', 'task']),
|
|
77
|
+
capability('wake_resume', 'hermes.resume-help.v1', ['resume', '--help'], ['resume']),
|
|
78
|
+
capability('cancellation', 'hermes.cron-cancel-help.v1', ['cron', '--help'], ['pause', 'remove']),
|
|
79
|
+
capability('session_scoped_cleanup', 'hermes.session-cleanup-help.v1', ['sessions', '--help'], ['sessions', 'delete']),
|
|
80
|
+
capability('initiating_agent_delivery', 'hermes.origin-delivery-help.v1', ['cron', '--help'], ['origin', 'deliver']),
|
|
81
|
+
]),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
function getHarnessProbeSpec(harness) {
|
|
85
|
+
const spec = HARNESS_PROBE_SPECS[harness];
|
|
86
|
+
if (!spec) throw new TypeError(`Unsupported harness: ${harness}`);
|
|
87
|
+
return spec;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function assertBoundedPositiveInteger(value, name, maximum) {
|
|
91
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
|
|
92
|
+
throw new TypeError(`${name} must be an integer between 1 and ${maximum}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isHostileOutput(output) {
|
|
97
|
+
return SECRET_PATTERNS.some((pattern) => pattern.test(output))
|
|
98
|
+
|| PRIVATE_PATH_PATTERN.test(output)
|
|
99
|
+
|| [...output].some((character) => {
|
|
100
|
+
const code = character.codePointAt(0);
|
|
101
|
+
return (code >= 0 && code <= 8)
|
|
102
|
+
|| code === 11
|
|
103
|
+
|| code === 12
|
|
104
|
+
|| (code >= 14 && code <= 31)
|
|
105
|
+
|| code === 127;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeExecution(response, maxOutputBytes) {
|
|
110
|
+
if (!response || typeof response !== 'object' || Array.isArray(response)) {
|
|
111
|
+
return { complete: false, reason: 'MALFORMED_RESULT' };
|
|
112
|
+
}
|
|
113
|
+
if (response.timedOut === true) return { complete: false, reason: 'PROBE_TIMEOUT' };
|
|
114
|
+
if (response.errorCode === 'ENOENT') return { complete: false, reason: 'EXECUTABLE_UNAVAILABLE' };
|
|
115
|
+
if (typeof response.stdout !== 'string' || typeof response.stderr !== 'string') {
|
|
116
|
+
return { complete: false, reason: 'MALFORMED_RESULT' };
|
|
117
|
+
}
|
|
118
|
+
const outputBytes = Buffer.byteLength(response.stdout, 'utf8') + Buffer.byteLength(response.stderr, 'utf8');
|
|
119
|
+
if (outputBytes > maxOutputBytes) return { complete: false, reason: 'OUTPUT_LIMIT' };
|
|
120
|
+
const output = `${response.stdout}\n${response.stderr}`;
|
|
121
|
+
if (isHostileOutput(output)) return { complete: false, reason: 'HOSTILE_OUTPUT' };
|
|
122
|
+
if (response.exitCode !== 0) return { complete: false, reason: 'PROBE_FAILED' };
|
|
123
|
+
return {
|
|
124
|
+
complete: true,
|
|
125
|
+
stdout: response.stdout,
|
|
126
|
+
stderr: response.stderr,
|
|
127
|
+
executableIdentity: response.executableIdentity,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function executeBounded(execute, requestBase, timeoutMs) {
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
let timer;
|
|
134
|
+
const timeout = new Promise((resolve) => {
|
|
135
|
+
timer = setTimeout(() => {
|
|
136
|
+
controller.abort();
|
|
137
|
+
resolve({ timedOut: true, stdout: '', stderr: '' });
|
|
138
|
+
}, timeoutMs);
|
|
139
|
+
});
|
|
140
|
+
const request = Object.freeze({ ...requestBase, signal: controller.signal });
|
|
141
|
+
try {
|
|
142
|
+
return await Promise.race([
|
|
143
|
+
Promise.resolve().then(() => execute(request)),
|
|
144
|
+
timeout,
|
|
145
|
+
]);
|
|
146
|
+
} catch (_error) {
|
|
147
|
+
return { exitCode: null, stdout: '', stderr: '', errorCode: 'EXECUTION_ERROR' };
|
|
148
|
+
} finally {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function requestFor({ harness, kind, probeId, command, args, timeoutMs, maxOutputBytes }) {
|
|
154
|
+
return {
|
|
155
|
+
harness,
|
|
156
|
+
kind,
|
|
157
|
+
probeId,
|
|
158
|
+
command,
|
|
159
|
+
args: Object.freeze([...args]),
|
|
160
|
+
timeoutMs,
|
|
161
|
+
maxOutputBytes,
|
|
162
|
+
sideEffectFree: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function incompleteCapabilities(spec, reason) {
|
|
167
|
+
return spec.capabilities.map((item) => ({
|
|
168
|
+
id: item.id,
|
|
169
|
+
available: false,
|
|
170
|
+
status: 'INCOMPLETE',
|
|
171
|
+
reason,
|
|
172
|
+
probe_id: item.probeId,
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function buildResult({ harness, spec, observedAt, status, availability, identity, version, capabilities }) {
|
|
177
|
+
return createProbeResult({
|
|
178
|
+
probeRevision: PROBE_REVISION,
|
|
179
|
+
harnessId: harness,
|
|
180
|
+
status,
|
|
181
|
+
availability,
|
|
182
|
+
observedAt,
|
|
183
|
+
executable: { command: spec.command, identity, version },
|
|
184
|
+
capabilities,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function parseVersion(stdout, pattern) {
|
|
189
|
+
const normalized = stdout.trim();
|
|
190
|
+
if (normalized.includes('\n') || normalized.includes('\r')) return null;
|
|
191
|
+
const match = pattern.exec(normalized);
|
|
192
|
+
return match ? match[1] : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function capabilityFromExecution(item, execution, identity) {
|
|
196
|
+
let status = 'INCOMPLETE';
|
|
197
|
+
let reason = execution.reason;
|
|
198
|
+
let available = false;
|
|
199
|
+
if (execution.complete && execution.executableIdentity !== identity) {
|
|
200
|
+
reason = 'EXECUTABLE_IDENTITY_CHANGED';
|
|
201
|
+
} else if (execution.complete) {
|
|
202
|
+
const output = execution.stdout.toLowerCase();
|
|
203
|
+
available = item.requiredTokens.every((token) => output.includes(token.toLowerCase()));
|
|
204
|
+
status = available ? 'AVAILABLE' : 'UNAVAILABLE';
|
|
205
|
+
reason = available ? 'PROBED_SUPPORTED' : 'PROBED_UNSUPPORTED';
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
id: item.id,
|
|
209
|
+
available,
|
|
210
|
+
status,
|
|
211
|
+
reason,
|
|
212
|
+
probe_id: item.probeId,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function probeCapability(item, context, cache) {
|
|
217
|
+
const { execute, harness, spec, timeoutMs, maxOutputBytes, identity } = context;
|
|
218
|
+
const cacheKey = JSON.stringify(item.args);
|
|
219
|
+
let response = cache.get(cacheKey);
|
|
220
|
+
if (!response) {
|
|
221
|
+
response = executeBounded(execute, requestFor({
|
|
222
|
+
harness,
|
|
223
|
+
kind: 'behavior',
|
|
224
|
+
probeId: item.probeId,
|
|
225
|
+
command: spec.command,
|
|
226
|
+
args: item.args,
|
|
227
|
+
timeoutMs,
|
|
228
|
+
maxOutputBytes,
|
|
229
|
+
}), timeoutMs);
|
|
230
|
+
cache.set(cacheKey, response);
|
|
231
|
+
}
|
|
232
|
+
const execution = normalizeExecution(await response, maxOutputBytes);
|
|
233
|
+
return capabilityFromExecution(item, execution, identity);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function probeCapabilities(context) {
|
|
237
|
+
const cache = new Map();
|
|
238
|
+
const capabilities = [];
|
|
239
|
+
for (const item of context.spec.capabilities) {
|
|
240
|
+
capabilities.push(await probeCapability(item, context, cache));
|
|
241
|
+
}
|
|
242
|
+
return capabilities;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function probeHarness(options) {
|
|
246
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
247
|
+
throw new TypeError('probe options must be an object');
|
|
248
|
+
}
|
|
249
|
+
const { harness, execute } = options;
|
|
250
|
+
const spec = getHarnessProbeSpec(harness);
|
|
251
|
+
if (typeof execute !== 'function') throw new TypeError('execute must be a function');
|
|
252
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
253
|
+
const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
254
|
+
assertBoundedPositiveInteger(timeoutMs, 'timeoutMs', MAX_TIMEOUT_MS);
|
|
255
|
+
assertBoundedPositiveInteger(maxOutputBytes, 'maxOutputBytes', MAX_OUTPUT_BYTES);
|
|
256
|
+
const observedAt = options.observedAt ?? new Date().toISOString();
|
|
257
|
+
|
|
258
|
+
const versionResponse = await executeBounded(execute, requestFor({
|
|
259
|
+
harness,
|
|
260
|
+
kind: 'version',
|
|
261
|
+
probeId: `${harness}.version.v1`,
|
|
262
|
+
command: spec.command,
|
|
263
|
+
args: spec.versionArgs,
|
|
264
|
+
timeoutMs,
|
|
265
|
+
maxOutputBytes,
|
|
266
|
+
}), timeoutMs);
|
|
267
|
+
const versionExecution = normalizeExecution(versionResponse, maxOutputBytes);
|
|
268
|
+
if (!versionExecution.complete) {
|
|
269
|
+
return buildResult({
|
|
270
|
+
harness,
|
|
271
|
+
spec,
|
|
272
|
+
observedAt,
|
|
273
|
+
status: 'INCOMPLETE',
|
|
274
|
+
availability: 'UNAVAILABLE',
|
|
275
|
+
identity: null,
|
|
276
|
+
version: null,
|
|
277
|
+
capabilities: incompleteCapabilities(spec, versionExecution.reason),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const identity = versionExecution.executableIdentity;
|
|
281
|
+
const version = parseVersion(versionExecution.stdout || versionExecution.stderr, spec.versionPattern);
|
|
282
|
+
if (!EXECUTABLE_IDENTITY_PATTERN.test(identity || '')) {
|
|
283
|
+
return buildResult({
|
|
284
|
+
harness,
|
|
285
|
+
spec,
|
|
286
|
+
observedAt,
|
|
287
|
+
status: 'INCOMPLETE',
|
|
288
|
+
availability: 'UNAVAILABLE',
|
|
289
|
+
identity: null,
|
|
290
|
+
version,
|
|
291
|
+
capabilities: incompleteCapabilities(spec, 'EXECUTABLE_IDENTITY_UNAVAILABLE'),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if (!version) {
|
|
295
|
+
return buildResult({
|
|
296
|
+
harness,
|
|
297
|
+
spec,
|
|
298
|
+
observedAt,
|
|
299
|
+
status: 'INCOMPLETE',
|
|
300
|
+
availability: 'UNAVAILABLE',
|
|
301
|
+
identity,
|
|
302
|
+
version: null,
|
|
303
|
+
capabilities: incompleteCapabilities(spec, 'MALFORMED_VERSION'),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const capabilities = await probeCapabilities({
|
|
308
|
+
execute,
|
|
309
|
+
harness,
|
|
310
|
+
spec,
|
|
311
|
+
timeoutMs,
|
|
312
|
+
maxOutputBytes,
|
|
313
|
+
identity,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return buildResult({
|
|
317
|
+
harness,
|
|
318
|
+
spec,
|
|
319
|
+
observedAt,
|
|
320
|
+
status: capabilities.some((item) => item.status === 'INCOMPLETE') ? 'INCOMPLETE' : 'PASS',
|
|
321
|
+
availability: 'AVAILABLE',
|
|
322
|
+
identity,
|
|
323
|
+
version,
|
|
324
|
+
capabilities,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function probeAllHarnesses(options) {
|
|
329
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
330
|
+
throw new TypeError('probe options must be an object');
|
|
331
|
+
}
|
|
332
|
+
return Promise.all(HARNESS_IDS.map((harness) => probeHarness({ ...options, harness })));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
module.exports = {
|
|
336
|
+
CAPABILITY_IDS,
|
|
337
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
338
|
+
DEFAULT_TIMEOUT_MS,
|
|
339
|
+
MAX_OUTPUT_BYTES,
|
|
340
|
+
MAX_TIMEOUT_MS,
|
|
341
|
+
HARNESS_IDS,
|
|
342
|
+
HARNESS_PROBE_SPECS,
|
|
343
|
+
PROBE_REVISION,
|
|
344
|
+
getHarnessProbeSpec,
|
|
345
|
+
probeAllHarnesses,
|
|
346
|
+
probeHarness,
|
|
347
|
+
};
|
package/lib/codex-skills.js
CHANGED
|
@@ -135,9 +135,9 @@ function resolveCodexRepoSkillsDir(projectRoot) {
|
|
|
135
135
|
* @param {boolean} [params.clean=false] - Remove stale canonical-managed dirs first.
|
|
136
136
|
* @returns {{targetSkillsDir: string, written: string[]}}
|
|
137
137
|
*/
|
|
138
|
-
function populateCodexRepoSkills({ sourceRoot, projectRoot, clean = false }) {
|
|
138
|
+
function populateCodexRepoSkills({ sourceRoot, projectRoot, clean = false, excludeRelativePaths }) {
|
|
139
139
|
const targetSkillsDir = resolveCodexRepoSkillsDir(projectRoot);
|
|
140
|
-
const { written } = populateAgentSkills({ sourceRoot, targetSkillsDir, clean });
|
|
140
|
+
const { written } = populateAgentSkills({ sourceRoot, targetSkillsDir, clean, excludeRelativePaths });
|
|
141
141
|
return { targetSkillsDir, written };
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -39,6 +39,7 @@ const commands = [
|
|
|
39
39
|
{ file: "explain.js", module: require("./explain") },
|
|
40
40
|
{ file: "export.js", module: require("./export") },
|
|
41
41
|
{ file: "gate.js", module: require("./gate") },
|
|
42
|
+
{ file: "github.js", module: require("./github") },
|
|
42
43
|
{ file: "hooks.js", module: require("./hooks") },
|
|
43
44
|
{ file: "inbox.js", module: require("./inbox") },
|
|
44
45
|
{ file: "init.js", module: require("./init") },
|
|
@@ -13,6 +13,7 @@ const { existsSync, readdirSync } = require('node:fs');
|
|
|
13
13
|
const path = require('node:path');
|
|
14
14
|
const { normalizeStageId } = require('../workflow/stages');
|
|
15
15
|
const { ensureForgeHome, isMutatingVerb } = require('../activation/ensure-forge-home');
|
|
16
|
+
const { createGithubContext } = require('../github-context');
|
|
16
17
|
|
|
17
18
|
// Static command manifest (bundleable fast path). This is a static require so
|
|
18
19
|
// `bun build --compile` can bundle the command graph; the file is generated by
|
|
@@ -36,6 +37,7 @@ try {
|
|
|
36
37
|
* @property {function(Array, Object, string, Object=): Promise<*>} handler - Async command handler (4th arg: resolved command opts)
|
|
37
38
|
* @property {string} [usage] - Usage string (optional)
|
|
38
39
|
* @property {Object<string, string>} [flags] - Flag descriptions (optional)
|
|
40
|
+
* @property {boolean|function(Array, Object, Object): boolean} [githubAuth] - Whether this invocation needs repository GitHub context
|
|
39
41
|
*/
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -57,6 +59,9 @@ function validateCommand(mod) {
|
|
|
57
59
|
if (typeof mod.handler !== 'function') {
|
|
58
60
|
return { valid: false, reason: 'missing or invalid "handler" export' };
|
|
59
61
|
}
|
|
62
|
+
if (mod.githubAuth !== undefined && !['boolean', 'function'].includes(typeof mod.githubAuth)) {
|
|
63
|
+
return { valid: false, reason: 'invalid "githubAuth" export: expected boolean or function' };
|
|
64
|
+
}
|
|
60
65
|
return { valid: true };
|
|
61
66
|
}
|
|
62
67
|
|
|
@@ -174,6 +179,31 @@ function isStageCommand(commandName) {
|
|
|
174
179
|
return normalizeStageId(commandName) !== null;
|
|
175
180
|
}
|
|
176
181
|
|
|
182
|
+
function isHelpInvocation(args, flags) {
|
|
183
|
+
if (flags.help || flags['--help'] || flags['-h']) return true;
|
|
184
|
+
// Child arguments belong to the launcher, including its help flags.
|
|
185
|
+
const delimiter = args.indexOf('--');
|
|
186
|
+
const ownArgs = delimiter === -1 ? args : args.slice(0, delimiter);
|
|
187
|
+
return ownArgs.includes('--help') || ownArgs.includes('-h');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function enforceCommandStage(options, commandName, args, flags, projectRoot, command) {
|
|
191
|
+
if (typeof options.enforceStage !== 'function' || !isStageCommand(commandName)) return null;
|
|
192
|
+
return options.enforceStage({ commandName, args, flags, projectRoot, command });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function resolveCommandOptions(command, commandName, args, flags, projectRoot, options) {
|
|
196
|
+
const commandOpts = options.commandOpts ?? {};
|
|
197
|
+
if (!command.githubAuth || isHelpInvocation(args, flags)) return commandOpts;
|
|
198
|
+
const required = typeof command.githubAuth === 'function'
|
|
199
|
+
? await command.githubAuth(args, flags, { commandName, projectRoot, command })
|
|
200
|
+
: command.githubAuth;
|
|
201
|
+
if (typeof required !== 'boolean') throw new TypeError('Invalid GitHub auth predicate result');
|
|
202
|
+
if (!required) return commandOpts;
|
|
203
|
+
const githubContext = await (options.prepareGithubContext || createGithubContext)(projectRoot);
|
|
204
|
+
return { ...commandOpts, githubContext };
|
|
205
|
+
}
|
|
206
|
+
|
|
177
207
|
async function executeCommand(commands, commandName, args, flags, projectRoot, options = {}) {
|
|
178
208
|
const command = commands.get(commandName);
|
|
179
209
|
if (!command) {
|
|
@@ -181,23 +211,13 @@ async function executeCommand(commands, commandName, args, flags, projectRoot, o
|
|
|
181
211
|
}
|
|
182
212
|
|
|
183
213
|
try {
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
command,
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
if (enforcement?.allowed === false) {
|
|
195
|
-
return {
|
|
196
|
-
success: false,
|
|
197
|
-
error: enforcement.error ?? `Stage ${commandName} is blocked.`,
|
|
198
|
-
enforcement,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
214
|
+
const enforcement = await enforceCommandStage(options, commandName, args, flags, projectRoot, command);
|
|
215
|
+
if (enforcement?.allowed === false) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
error: enforcement.error ?? `Stage ${commandName} is blocked.`,
|
|
219
|
+
enforcement,
|
|
220
|
+
};
|
|
201
221
|
}
|
|
202
222
|
|
|
203
223
|
// Lazy `.forge/` home (activation foundation): the FIRST mutating verb in a
|
|
@@ -217,7 +237,17 @@ async function executeCommand(commands, commandName, args, flags, projectRoot, o
|
|
|
217
237
|
// alias handlers read it (shouldUseKernelBroker(opts)); other handlers ignore
|
|
218
238
|
// it. Backward-compatible: defaults to {} so existing 3-arg handlers are
|
|
219
239
|
// unaffected.
|
|
220
|
-
|
|
240
|
+
let commandOpts;
|
|
241
|
+
try {
|
|
242
|
+
commandOpts = await resolveCommandOptions(command, commandName, args, flags, projectRoot, options);
|
|
243
|
+
} catch {
|
|
244
|
+
// Even injected errors can contain subprocess output or credentials.
|
|
245
|
+
return {
|
|
246
|
+
success: false,
|
|
247
|
+
error: 'Unable to verify this clone\'s GitHub account. Run forge github status for recovery.',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const result = await command.handler(args, flags, projectRoot, commandOpts);
|
|
221
251
|
|
|
222
252
|
// Record stage COMPLETION into the kernel only when the command SUCCEEDED, so
|
|
223
253
|
// a stage counts as 'done' (which gates ship/review) only when its command
|
package/lib/commands/clean.js
CHANGED
|
@@ -617,6 +617,52 @@ function createIssueCloser(projectRoot, runFile, opts) {
|
|
|
617
617
|
};
|
|
618
618
|
}
|
|
619
619
|
|
|
620
|
+
async function reconcileDeadClaims(projectRoot, runFile, opts = {}) {
|
|
621
|
+
try {
|
|
622
|
+
if (typeof opts._reconcileClaims === 'function') {
|
|
623
|
+
return await opts._reconcileClaims({ projectRoot });
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
let driver = opts._kernelDriver;
|
|
627
|
+
let owned = false;
|
|
628
|
+
if (!driver) {
|
|
629
|
+
const commonDirOutput = tryRun(runFile, 'git', ['-C', projectRoot, 'rev-parse', '--git-common-dir']);
|
|
630
|
+
if (!commonDirOutput) {
|
|
631
|
+
return { examined: 0, released: [] };
|
|
632
|
+
}
|
|
633
|
+
const gitCommonDir = path.isAbsolute(commonDirOutput)
|
|
634
|
+
? commonDirOutput
|
|
635
|
+
: path.resolve(projectRoot, commonDirOutput);
|
|
636
|
+
const {
|
|
637
|
+
buildMigratedKernelIssueDeps,
|
|
638
|
+
resolveKernelDatabasePath,
|
|
639
|
+
} = require('../kernel/cli-broker-factory');
|
|
640
|
+
const databasePath = resolveKernelDatabasePath({ gitCommonDir });
|
|
641
|
+
const fsApi = opts._fs || fs;
|
|
642
|
+
if (!fsApi.existsSync(databasePath)) return { examined: 0, released: [] };
|
|
643
|
+
const deps = await buildMigratedKernelIssueDeps({ projectRoot, gitCommonDir, databasePath });
|
|
644
|
+
driver = deps.kernelDriver;
|
|
645
|
+
owned = true;
|
|
646
|
+
}
|
|
647
|
+
if (!driver) return { examined: 0, released: [] };
|
|
648
|
+
|
|
649
|
+
try {
|
|
650
|
+
const { reconcileKernelClaims } = require('../kernel/claim-reconciler');
|
|
651
|
+
return await reconcileKernelClaims({
|
|
652
|
+
driver,
|
|
653
|
+
fsApi: opts._fs || fs,
|
|
654
|
+
manifestDir: opts._manifestDir,
|
|
655
|
+
isProcessAlive: opts._isProcessAlive,
|
|
656
|
+
getProcessIdentity: opts._getProcessIdentity,
|
|
657
|
+
});
|
|
658
|
+
} finally {
|
|
659
|
+
if (owned && typeof driver.close === 'function') driver.close();
|
|
660
|
+
}
|
|
661
|
+
} catch (_e) { /* best-effort: unverifiable authority never aborts clean */ // NOSONAR S2486
|
|
662
|
+
return { examined: 0, released: [] };
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
620
666
|
/**
|
|
621
667
|
* Main handler for the clean command.
|
|
622
668
|
* @param {string[]} _args - Positional arguments (unused)
|
|
@@ -654,7 +700,10 @@ async function cleanWorktrees(worktreesDir, runFile, fsApi, dryRun, opts, closer
|
|
|
654
700
|
// ancestry fast path instead of falling through to the slower squash tier.
|
|
655
701
|
mergedBranches = mergedOut.split('\n').map(b => b.trim().replace(/^[*+]\s*/, '')).filter(Boolean);
|
|
656
702
|
}
|
|
657
|
-
const
|
|
703
|
+
const ghRunner = opts.githubContext?.bound
|
|
704
|
+
? (_cmd, args, options) => opts.githubContext.runGh(args, options)
|
|
705
|
+
: runFile;
|
|
706
|
+
const ghMergedPrs = getGhMergedPrs(ghRunner);
|
|
658
707
|
const ctx = { defaultBranch, mergedBranches, ghMergedPrs, runFile };
|
|
659
708
|
const isMergedFn = opts._isMerged || (branch => detectMerged(branch, ctx));
|
|
660
709
|
|
|
@@ -703,6 +752,11 @@ async function handler(_args, flags, projectRoot, opts = {}) {
|
|
|
703
752
|
}
|
|
704
753
|
const { cleaned, active, survivors, dirty, closedIssues } = scan;
|
|
705
754
|
|
|
755
|
+
// Claim repair is an explicit clean-lifecycle maintenance action. Run after
|
|
756
|
+
// worktree removal so a newly-missing linked checkout can be observed, while
|
|
757
|
+
// dry-run remains mutation-free. Evidence gaps and Kernel failures fail closed.
|
|
758
|
+
if (!dryRun) await reconcileDeadClaims(projectRoot, runFile, opts);
|
|
759
|
+
|
|
706
760
|
// Post-merge master auto-update (default-on; skipped in dry-run).
|
|
707
761
|
let masterSync = null;
|
|
708
762
|
if (masterSyncEnabled && !dryRun) {
|
|
@@ -723,6 +777,7 @@ function finalize(summary, masterSync) {
|
|
|
723
777
|
|
|
724
778
|
module.exports = {
|
|
725
779
|
name: 'clean',
|
|
780
|
+
githubAuth: true,
|
|
726
781
|
description: 'Remove worktrees for merged branches (squash-aware) and fast-forward the default branch',
|
|
727
782
|
usage: 'forge clean [--dry-run] [--no-master-sync]',
|
|
728
783
|
flags: {
|
|
@@ -739,6 +794,7 @@ module.exports = {
|
|
|
739
794
|
getGhMergedPrs,
|
|
740
795
|
prEvidenceFor,
|
|
741
796
|
createIssueCloser,
|
|
797
|
+
reconcileDeadClaims,
|
|
742
798
|
isSquashMerged,
|
|
743
799
|
hasMergedPrEvidence,
|
|
744
800
|
isUnstartedBranch,
|
package/lib/commands/doctor.js
CHANGED
|
@@ -38,10 +38,39 @@ function buildMemoryCheck(projectRoot, env) {
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
const serverPath = graphiti.mcpServerPath;
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
?
|
|
44
|
-
:
|
|
41
|
+
const path = require('node:path');
|
|
42
|
+
const resolvedServerPath = path.isAbsolute(serverPath)
|
|
43
|
+
? serverPath
|
|
44
|
+
: path.resolve(projectRoot, serverPath);
|
|
45
|
+
const serverPathExists = fs.existsSync(resolvedServerPath);
|
|
46
|
+
let serverPathIsDirectory = false;
|
|
47
|
+
if (serverPathExists) {
|
|
48
|
+
try {
|
|
49
|
+
serverPathIsDirectory = fs.statSync(resolvedServerPath).isDirectory();
|
|
50
|
+
} catch {
|
|
51
|
+
serverPathIsDirectory = false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const entrypointPath = path.join(resolvedServerPath, 'main.py');
|
|
55
|
+
let entrypointExists = false;
|
|
56
|
+
if (serverPathIsDirectory) {
|
|
57
|
+
try {
|
|
58
|
+
entrypointExists = fs.statSync(entrypointPath).isFile();
|
|
59
|
+
} catch {
|
|
60
|
+
entrypointExists = false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const serverReady = serverPathExists && serverPathIsDirectory && entrypointExists;
|
|
64
|
+
let reach;
|
|
65
|
+
if (!serverPathExists) {
|
|
66
|
+
reach = `mcp_server path not found locally (${serverPath}); run it before agents can use graph memory`;
|
|
67
|
+
} else if (!serverPathIsDirectory) {
|
|
68
|
+
reach = `mcp_server path is not a directory (${serverPath})`;
|
|
69
|
+
} else if (!entrypointExists) {
|
|
70
|
+
reach = `mcp_server entrypoint not found (${path.join(serverPath, 'main.py')})`;
|
|
71
|
+
} else {
|
|
72
|
+
reach = `mcp_server present at ${serverPath}`;
|
|
73
|
+
}
|
|
45
74
|
return {
|
|
46
75
|
// Reflect reality in the line symbol: a graphiti backend whose MCP server
|
|
47
76
|
// isn't present yet renders as `!` (warn), not a misleading `✓`. This does
|
|
@@ -49,13 +78,15 @@ function buildMemoryCheck(projectRoot, env) {
|
|
|
49
78
|
// filesystem-class check (see buildDoctorReport), so an uninstalled opt-in
|
|
50
79
|
// memory server warns without failing `forge doctor`.
|
|
51
80
|
id: 'memory-backend',
|
|
52
|
-
ok:
|
|
81
|
+
ok: serverReady,
|
|
53
82
|
backend,
|
|
54
83
|
serverPathExists,
|
|
84
|
+
serverPathIsDirectory,
|
|
85
|
+
entrypointExists,
|
|
55
86
|
detail: `memory backend: graphiti — served to agents via the graphiti-memory MCP server; ${reach}`,
|
|
56
87
|
};
|
|
57
88
|
} catch (err) {
|
|
58
|
-
const backend = memoryRouter.resolveMemoryBackend({ projectRoot, env, warn: () => {} });
|
|
89
|
+
const backend = err.backend || memoryRouter.resolveMemoryBackend({ projectRoot, env, warn: () => {} });
|
|
59
90
|
return { id: 'memory-backend', ok: false, backend, detail: err.message };
|
|
60
91
|
}
|
|
61
92
|
}
|