forge-workflow 0.0.5 → 0.0.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/.claude/commands/dev.md +6 -1
- package/.claude/commands/plan.md +59 -14
- package/.claude/commands/premerge.md +10 -0
- package/.claude/commands/review.md +7 -1
- package/.claude/commands/ship.md +95 -47
- package/.claude/commands/status.md +42 -0
- package/.claude/commands/validate.md +7 -1
- package/.claude/commands/verify.md +52 -4
- package/.claude/rules/workflow.md +16 -0
- package/.claude/scripts/greptile-resolve.sh +32 -0
- package/.cline/workflows/dev.md +6 -1
- package/.cline/workflows/plan.md +59 -14
- package/.cline/workflows/premerge.md +10 -0
- package/.cline/workflows/review.md +7 -1
- package/.cline/workflows/ship.md +95 -47
- package/.cline/workflows/status.md +42 -0
- package/.cline/workflows/validate.md +7 -1
- package/.cline/workflows/verify.md +52 -4
- package/.codex/skills/dev/SKILL.md +6 -1
- package/.codex/skills/plan/SKILL.md +59 -14
- package/.codex/skills/premerge/SKILL.md +10 -0
- package/.codex/skills/review/SKILL.md +7 -1
- package/.codex/skills/ship/SKILL.md +95 -47
- package/.codex/skills/status/SKILL.md +42 -0
- package/.codex/skills/validate/SKILL.md +7 -1
- package/.codex/skills/verify/SKILL.md +52 -4
- package/.cursor/commands/dev.md +6 -1
- package/.cursor/commands/plan.md +59 -14
- package/.cursor/commands/premerge.md +10 -0
- package/.cursor/commands/review.md +7 -1
- package/.cursor/commands/ship.md +95 -47
- package/.cursor/commands/status.md +42 -0
- package/.cursor/commands/validate.md +7 -1
- package/.cursor/commands/verify.md +52 -4
- package/.cursorrules +149 -0
- package/.github/prompts/dev.prompt.md +6 -1
- package/.github/prompts/plan.prompt.md +59 -14
- package/.github/prompts/premerge.prompt.md +10 -0
- package/.github/prompts/review.prompt.md +7 -1
- package/.github/prompts/ship.prompt.md +95 -47
- package/.github/prompts/status.prompt.md +42 -0
- package/.github/prompts/validate.prompt.md +7 -1
- package/.github/prompts/verify.prompt.md +52 -4
- package/.kilocode/workflows/dev.md +6 -1
- package/.kilocode/workflows/plan.md +59 -14
- package/.kilocode/workflows/premerge.md +10 -0
- package/.kilocode/workflows/review.md +7 -1
- package/.kilocode/workflows/ship.md +95 -47
- package/.kilocode/workflows/status.md +42 -0
- package/.kilocode/workflows/validate.md +7 -1
- package/.kilocode/workflows/verify.md +52 -4
- package/.opencode/commands/dev.md +6 -1
- package/.opencode/commands/plan.md +59 -14
- package/.opencode/commands/premerge.md +10 -0
- package/.opencode/commands/review.md +7 -1
- package/.opencode/commands/ship.md +95 -47
- package/.opencode/commands/status.md +42 -0
- package/.opencode/commands/validate.md +7 -1
- package/.opencode/commands/verify.md +52 -4
- package/.roo/commands/dev.md +6 -1
- package/.roo/commands/plan.md +59 -14
- package/.roo/commands/premerge.md +10 -0
- package/.roo/commands/review.md +7 -1
- package/.roo/commands/ship.md +95 -47
- package/.roo/commands/status.md +42 -0
- package/.roo/commands/validate.md +7 -1
- package/.roo/commands/verify.md +52 -4
- package/AGENTS.md +97 -0
- package/CLAUDE.md +10 -0
- package/README.md +2 -2
- package/bin/forge-cmd.js +5 -1
- package/bin/forge-preflight.js +15 -2
- package/bin/forge.js +211 -9
- package/docs/ENHANCED_ONBOARDING.md +96 -86
- package/docs/ROADMAP.md +2 -2
- package/docs/TOOLCHAIN.md +23 -0
- package/docs/VALIDATION.md +1 -1
- package/lefthook.yml +11 -0
- package/lib/agents/README.md +46 -1
- package/lib/agents/cline.plugin.json +11 -4
- package/lib/agents/codex.plugin.json +2 -2
- package/lib/agents/copilot.plugin.json +5 -5
- package/lib/agents/cursor.plugin.json +1 -1
- package/lib/agents/kilocode.plugin.json +1 -1
- package/lib/agents/opencode.plugin.json +7 -4
- package/lib/agents/roo.plugin.json +10 -3
- package/lib/agents-config.js +129 -81
- package/lib/codex-skills.js +50 -0
- package/lib/commands/_registry.js +173 -0
- package/lib/commands/clean.js +181 -0
- package/lib/commands/commands-reset.js +147 -0
- package/lib/commands/dev.js +84 -0
- package/lib/commands/plan.js +18 -0
- package/lib/commands/push.js +196 -0
- package/lib/commands/recommend.js +1 -1
- package/lib/commands/setup.js +4295 -0
- package/lib/commands/ship.js +20 -0
- package/lib/commands/status.js +210 -44
- package/lib/commands/sync.js +71 -0
- package/lib/commands/team.js +37 -0
- package/lib/commands/test.js +207 -0
- package/lib/commands/validate.js +13 -0
- package/lib/commands/worktree.js +310 -0
- package/lib/detect-agent.js +38 -8
- package/lib/detection-utils.js +405 -0
- package/lib/docs-command.js +51 -0
- package/lib/docs-copy.js +50 -0
- package/lib/file-utils.js +260 -0
- package/lib/forge-context.js +42 -0
- package/lib/freshness-token.js +148 -0
- package/lib/frontmatter.js +79 -0
- package/lib/greptile-match.js +80 -0
- package/lib/husky-migration.js +113 -12
- package/lib/lefthook-check.js +27 -6
- package/lib/plugin-manager.js +225 -72
- package/lib/project-discovery.js +39 -5
- package/lib/reset.js +309 -0
- package/lib/runtime-health.js +305 -0
- package/lib/shell-utils.js +50 -0
- package/lib/task-ownership.js +117 -0
- package/lib/ui-utils.js +43 -0
- package/lib/validation-utils.js +163 -0
- package/lib/workflow/enforce-stage.js +179 -0
- package/lib/workflow/stages.js +201 -0
- package/lib/workflow/state.js +332 -0
- package/opencode.json +67 -0
- package/package.json +16 -6
- package/scripts/beads-context.sh +165 -22
- package/scripts/beads-context.test.js +5 -1
- package/scripts/check-agents.js +103 -0
- package/scripts/check-forge-token.js +98 -0
- package/scripts/conflict-detect.sh +2 -2
- package/scripts/dep-guard.sh +6 -28
- package/scripts/file-index.sh +117 -23
- package/scripts/forge-team/index.sh +86 -0
- package/scripts/forge-team/lib/agent-prompt.sh +52 -0
- package/scripts/forge-team/lib/claim.sh +256 -0
- package/scripts/forge-team/lib/dashboard.sh +341 -0
- package/scripts/forge-team/lib/epic.sh +332 -0
- package/scripts/forge-team/lib/hooks.sh +253 -0
- package/scripts/forge-team/lib/identity.sh +235 -0
- package/scripts/forge-team/lib/sync-github.sh +317 -0
- package/scripts/forge-team/lib/verify.sh +284 -0
- package/scripts/forge-team/lib/workload.sh +296 -0
- package/scripts/forge-team/tests/agent-prompt.test.sh +72 -0
- package/scripts/forge-team/tests/claim.test.sh +179 -0
- package/scripts/forge-team/tests/dashboard.test.sh +170 -0
- package/scripts/forge-team/tests/dispatcher.test.sh +79 -0
- package/scripts/forge-team/tests/epic.test.sh +176 -0
- package/scripts/forge-team/tests/hooks.test.sh +239 -0
- package/scripts/forge-team/tests/identity.test.sh +176 -0
- package/scripts/forge-team/tests/integration.test.sh +371 -0
- package/scripts/forge-team/tests/sync-github.test.sh +209 -0
- package/scripts/forge-team/tests/verify.test.sh +314 -0
- package/scripts/forge-team/tests/workflow-integration.test.sh +43 -0
- package/scripts/forge-team/tests/workload.test.sh +209 -0
- package/scripts/lib/eval-runner.js +39 -0
- package/scripts/lib/jsonl-lock.sh +48 -0
- package/scripts/lib/sanitize.sh +116 -0
- package/scripts/pr-coordinator.sh +756 -0
- package/scripts/smart-status.sh +58 -21
- package/scripts/sync-commands.js +49 -20
- package/scripts/sync-utils.sh +24 -29
- package/scripts/test.js +18 -1
package/scripts/smart-status.sh
CHANGED
|
@@ -20,8 +20,24 @@
|
|
|
20
20
|
|
|
21
21
|
set -euo pipefail
|
|
22
22
|
|
|
23
|
+
# ── Source cross-dev awareness scripts ────────────────────────────────
|
|
24
|
+
# file-index.sh: file_index_read for reading file index entries
|
|
25
|
+
# sync-utils.sh: get_session_identity for current developer identity
|
|
26
|
+
|
|
27
|
+
_SMART_STATUS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
if [ -f "$_SMART_STATUS_DIR/lib/sanitize.sh" ]; then
|
|
29
|
+
source "$_SMART_STATUS_DIR/lib/sanitize.sh"
|
|
30
|
+
fi
|
|
31
|
+
if [ -f "$_SMART_STATUS_DIR/file-index.sh" ]; then
|
|
32
|
+
source "$_SMART_STATUS_DIR/file-index.sh"
|
|
33
|
+
fi
|
|
34
|
+
if [ -f "$_SMART_STATUS_DIR/sync-utils.sh" ]; then
|
|
35
|
+
source "$_SMART_STATUS_DIR/sync-utils.sh"
|
|
36
|
+
fi
|
|
37
|
+
|
|
23
38
|
# ── Helpers ──────────────────────────────────────────────────────────────
|
|
24
39
|
|
|
40
|
+
if ! declare -F sanitize >/dev/null; then
|
|
25
41
|
# Sanitize a string: strip shell-injection patterns (OWASP A03)
|
|
26
42
|
# Removes: double quotes, $(...), backticks, semicolons, and newlines
|
|
27
43
|
sanitize() {
|
|
@@ -38,17 +54,6 @@ sanitize() {
|
|
|
38
54
|
val="$(printf '%s' "$val" | tr '\n' ' ')"
|
|
39
55
|
printf '%s' "$val"
|
|
40
56
|
}
|
|
41
|
-
|
|
42
|
-
# ── Source cross-dev awareness scripts ────────────────────────────────
|
|
43
|
-
# file-index.sh: file_index_read for reading file index entries
|
|
44
|
-
# sync-utils.sh: get_session_identity for current developer identity
|
|
45
|
-
|
|
46
|
-
_SMART_STATUS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
47
|
-
if [ -f "$_SMART_STATUS_DIR/file-index.sh" ]; then
|
|
48
|
-
source "$_SMART_STATUS_DIR/file-index.sh"
|
|
49
|
-
fi
|
|
50
|
-
if [ -f "$_SMART_STATUS_DIR/sync-utils.sh" ]; then
|
|
51
|
-
source "$_SMART_STATUS_DIR/sync-utils.sh"
|
|
52
57
|
fi
|
|
53
58
|
|
|
54
59
|
# ── Dependency check ────────────────────────────────────────────────────
|
|
@@ -112,6 +117,33 @@ done
|
|
|
112
117
|
|
|
113
118
|
# ── Fetch issues ────────────────────────────────────────────────────────
|
|
114
119
|
|
|
120
|
+
# Auto-recover beads database if Dolt server lost the database (e.g. branch
|
|
121
|
+
# switch, fresh clone, server restart). Checks for the specific "database
|
|
122
|
+
# not found" error, runs bd init --force + bd backup restore, then retries.
|
|
123
|
+
# Capture stderr into variable (2>&1 redirects stderr to stdout for capture,
|
|
124
|
+
# then >/dev/null discards original stdout so only errors remain).
|
|
125
|
+
if ! command -v "$BD" >/dev/null 2>&1; then
|
|
126
|
+
echo "Error: bd is required but not found." >&2
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
_bd_list_err="$("$BD" list --json --limit 0 2>&1 >/dev/null || true)"
|
|
131
|
+
if printf '%s' "$_bd_list_err" | grep -qi "database.*not found"; then
|
|
132
|
+
# Derive prefix from repo root directory name (not pwd, which could be a subdirectory)
|
|
133
|
+
_repo_root="$("$GIT" rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
134
|
+
_bd_prefix="$(basename "$_repo_root" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]-' '-' | sed 's/^-//;s/-$//')"
|
|
135
|
+
echo "Beads database not found — attempting auto-recovery..." >&2
|
|
136
|
+
if "$BD" init --force --prefix "$_bd_prefix" >/dev/null 2>&1; then
|
|
137
|
+
if [ -d "$_repo_root/.beads/backup" ] && ls "$_repo_root/.beads/backup"/*.jsonl >/dev/null 2>&1; then
|
|
138
|
+
"$BD" backup restore >/dev/null 2>&1 && echo "Beads: restored from backup." >&2 || echo "Beads: backup restore failed." >&2
|
|
139
|
+
else
|
|
140
|
+
echo "Beads: initialized fresh (no backup found)." >&2
|
|
141
|
+
fi
|
|
142
|
+
else
|
|
143
|
+
echo "Beads: auto-recovery failed — run 'bd doctor' manually." >&2
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
|
|
115
147
|
ISSUES_JSON="$("$BD" list --json --limit 0 2>/dev/null || echo '[]')"
|
|
116
148
|
|
|
117
149
|
# Bail early on empty
|
|
@@ -158,11 +190,15 @@ SCORED_JSON="$(printf '%s' "$ISSUES_JSON" | jq --argjson epic_stats "$EPIC_STATS
|
|
|
158
190
|
[.[] | . as $issue |
|
|
159
191
|
|
|
160
192
|
# Priority weight: P0=5, P1=4, P2=3, P3=2, P4=1, default=1
|
|
161
|
-
(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
193
|
+
# Normalize priority to number (beads 0.62+ sends numbers, older sends strings)
|
|
194
|
+
((.priority // 2) | if type == "string" then
|
|
195
|
+
(if . == "P0" then 0 elif . == "P1" then 1 elif . == "P2" then 2 elif . == "P3" then 3 elif . == "P4" then 4 else 5 end)
|
|
196
|
+
else . end) as $pri_num |
|
|
197
|
+
(if $pri_num == 0 then 5
|
|
198
|
+
elif $pri_num == 1 then 4
|
|
199
|
+
elif $pri_num == 2 then 3
|
|
200
|
+
elif $pri_num == 3 then 2
|
|
201
|
+
elif $pri_num == 4 then 1
|
|
166
202
|
else 1 end) as $priority_weight |
|
|
167
203
|
|
|
168
204
|
# Unblock chain: dependent_count + 1 (min 1).
|
|
@@ -172,9 +208,10 @@ SCORED_JSON="$(printf '%s' "$ISSUES_JSON" | jq --argjson epic_stats "$EPIC_STATS
|
|
|
172
208
|
(((.dependent_count // 0) + 1) | if . < 1 then 1 else . end) as $unblock_chain |
|
|
173
209
|
|
|
174
210
|
# Type weight: bug=1.2, feature=1.0, task=0.8, default=1.0
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
elif .type
|
|
211
|
+
# Handle null type from beads 0.62+
|
|
212
|
+
(if (.type // "task") == "bug" then 1.2
|
|
213
|
+
elif (.type // "task") == "feature" then 1.0
|
|
214
|
+
elif (.type // "task") == "task" then 0.8
|
|
178
215
|
else 1.0 end) as $type_weight |
|
|
179
216
|
|
|
180
217
|
# Status boost: in_progress=1.5, open=1.0, default=1.0
|
|
@@ -690,7 +727,7 @@ else
|
|
|
690
727
|
(if .status == "in_progress" then "RESUME"
|
|
691
728
|
elif (.dependent_count // 0) >= 2 then "UNBLOCK_CHAINS"
|
|
692
729
|
elif (.dependency_count // 0) > 0 and .status != "closed" then "BLOCKED"
|
|
693
|
-
elif .priority == "P4" then "BACKLOG"
|
|
730
|
+
elif (.priority == "P4" or .priority == 4) then "BACKLOG"
|
|
694
731
|
else "READY_WORK"
|
|
695
732
|
end) as $group |
|
|
696
733
|
# Flag in_progress issues that have active blockers
|
|
@@ -734,7 +771,7 @@ else
|
|
|
734
771
|
(if $item.blocked_resume then " !! BLOCKED by dependencies" else "" end) as $blocked_warn |
|
|
735
772
|
"ENTRY:" + $item.group + ":" +
|
|
736
773
|
$rank + ". [" + ($item.score | tostring) + "] " +
|
|
737
|
-
$item.id + " (" + ($item.priority // "-") + " " + ($item.type // "-") + ") -- " +
|
|
774
|
+
$item.id + " (" + (if ($item.priority | type) == "number" then "P" + ($item.priority | tostring) else ($item.priority // "-" | tostring) end) + " " + ($item.type // "-" | tostring) + ") -- " +
|
|
738
775
|
($item.title // "-") + " " + $status_tag + $stale + $blocked_warn + $unblocks
|
|
739
776
|
),
|
|
740
777
|
""
|
package/scripts/sync-commands.js
CHANGED
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const YAML = require('yaml');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const crypto = require('crypto');
|
|
18
|
+
const { listCodexSkillEntries } = require('../lib/codex-skills');
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
21
|
* Parse YAML frontmatter from a markdown string.
|
|
@@ -293,9 +297,14 @@ function adaptForAgent(agentName, frontmatter, body, commandName) {
|
|
|
293
297
|
|
|
294
298
|
// ---- Sync logic -----------------------------------------------------------------
|
|
295
299
|
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
|
|
300
|
+
function resolveCanonicalCommandsDir(repoRoot) {
|
|
301
|
+
const candidates = [
|
|
302
|
+
path.join(repoRoot, 'commands'),
|
|
303
|
+
path.join(repoRoot, '.claude', 'commands'),
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
return candidates.find(candidate => fs.existsSync(candidate)) || null;
|
|
307
|
+
}
|
|
299
308
|
|
|
300
309
|
/**
|
|
301
310
|
* Compute a content hash for change detection.
|
|
@@ -310,6 +319,25 @@ function contentHash(content) {
|
|
|
310
319
|
return crypto.createHash('sha256').update(normalized).digest('hex');
|
|
311
320
|
}
|
|
312
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Write the sync manifest for generated entries without rewriting command files.
|
|
324
|
+
*
|
|
325
|
+
* @param {string} repoRoot
|
|
326
|
+
* @param {SyncEntry[]} entries
|
|
327
|
+
*/
|
|
328
|
+
function writeSyncManifest(repoRoot, entries) {
|
|
329
|
+
const manifestDir = path.join(repoRoot, '.forge');
|
|
330
|
+
fs.mkdirSync(manifestDir, { recursive: true });
|
|
331
|
+
const manifestData = {
|
|
332
|
+
generatedAt: new Date().toISOString(),
|
|
333
|
+
files: entries.map((e) => path.relative(repoRoot, e.filePath).replace(/\\/g, '/')),
|
|
334
|
+
};
|
|
335
|
+
fs.writeFileSync(
|
|
336
|
+
path.join(manifestDir, 'sync-manifest.json'),
|
|
337
|
+
JSON.stringify(manifestData, null, 2) + '\n'
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
313
341
|
/**
|
|
314
342
|
* @typedef {Object} SyncEntry
|
|
315
343
|
* @property {string} agent - Agent slug
|
|
@@ -339,11 +367,11 @@ function contentHash(content) {
|
|
|
339
367
|
* - `check: true` — compares generated content with existing files, reports mismatches
|
|
340
368
|
* - default (both false) — writes files, creating directories as needed
|
|
341
369
|
*
|
|
342
|
-
* @param {{ dryRun: boolean, check: boolean, repoRoot: string }} options
|
|
370
|
+
* @param {{ dryRun: boolean, check: boolean, repoRoot: string, canonicalDir?: string | null }} options
|
|
343
371
|
* @returns {SyncResult}
|
|
344
372
|
*/
|
|
345
|
-
function syncCommands({ dryRun, check, repoRoot }) {
|
|
346
|
-
const commandsDir = path.join(repoRoot, '.claude', 'commands');
|
|
373
|
+
function syncCommands({ dryRun, check, repoRoot, canonicalDir = null }) {
|
|
374
|
+
const commandsDir = canonicalDir || resolveCanonicalCommandsDir(repoRoot) || path.join(repoRoot, '.claude', 'commands');
|
|
347
375
|
|
|
348
376
|
// Read all .md files from the commands directory
|
|
349
377
|
/** @type {string[]} */
|
|
@@ -474,16 +502,7 @@ function syncCommands({ dryRun, check, repoRoot }) {
|
|
|
474
502
|
|
|
475
503
|
// Write sync manifest — records every file generated so stale detection
|
|
476
504
|
// can identify orphaned files without false-flagging custom files.
|
|
477
|
-
|
|
478
|
-
fs.mkdirSync(manifestDir, { recursive: true });
|
|
479
|
-
const manifestData = {
|
|
480
|
-
generatedAt: new Date().toISOString(),
|
|
481
|
-
files: written.map((e) => path.relative(repoRoot, e.filePath).replace(/\\/g, '/')),
|
|
482
|
-
};
|
|
483
|
-
fs.writeFileSync(
|
|
484
|
-
path.join(manifestDir, 'sync-manifest.json'),
|
|
485
|
-
JSON.stringify(manifestData, null, 2) + '\n'
|
|
486
|
-
);
|
|
505
|
+
writeSyncManifest(repoRoot, written);
|
|
487
506
|
|
|
488
507
|
return { written, overwritten };
|
|
489
508
|
}
|
|
@@ -506,7 +525,7 @@ if (require.main === module) {
|
|
|
506
525
|
|
|
507
526
|
if (dryRun) {
|
|
508
527
|
if (result.planned.length === 0) {
|
|
509
|
-
console.log('No command files found in .claude/commands/');
|
|
528
|
+
console.log('No command files found in commands/ or .claude/commands/');
|
|
510
529
|
} else {
|
|
511
530
|
console.log('Dry run — files that would be generated:\n');
|
|
512
531
|
for (const entry of result.planned) {
|
|
@@ -516,7 +535,7 @@ if (require.main === module) {
|
|
|
516
535
|
}
|
|
517
536
|
} else if (check) {
|
|
518
537
|
if (result.empty) {
|
|
519
|
-
console.error('Error: no command files found in .claude/commands/ — cannot verify sync.');
|
|
538
|
+
console.error('Error: no command files found in commands/ or .claude/commands/ — cannot verify sync.');
|
|
520
539
|
process.exit(1);
|
|
521
540
|
}
|
|
522
541
|
if (result.manifestMissing) {
|
|
@@ -561,11 +580,21 @@ if (require.main === module) {
|
|
|
561
580
|
}
|
|
562
581
|
|
|
563
582
|
if (result.written.length === 0) {
|
|
564
|
-
console.log('No command files found in .claude/commands/');
|
|
583
|
+
console.log('No command files found in commands/ or .claude/commands/');
|
|
565
584
|
} else {
|
|
566
585
|
console.log(`Synced ${result.written.length} file(s) across agents.`);
|
|
567
586
|
}
|
|
568
587
|
}
|
|
569
588
|
}
|
|
570
589
|
|
|
571
|
-
module.exports = {
|
|
590
|
+
module.exports = {
|
|
591
|
+
parseFrontmatter,
|
|
592
|
+
buildFile,
|
|
593
|
+
AGENT_ADAPTERS,
|
|
594
|
+
adaptForAgent,
|
|
595
|
+
listCodexSkillEntries,
|
|
596
|
+
syncCommands,
|
|
597
|
+
contentHash,
|
|
598
|
+
resolveCanonicalCommandsDir,
|
|
599
|
+
writeSyncManifest,
|
|
600
|
+
};
|
package/scripts/sync-utils.sh
CHANGED
|
@@ -11,6 +11,10 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
|
11
11
|
set -euo pipefail
|
|
12
12
|
fi
|
|
13
13
|
|
|
14
|
+
# Source shared sanitize library
|
|
15
|
+
_SU_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
source "$_SU_SCRIPT_DIR/lib/sanitize.sh"
|
|
17
|
+
|
|
14
18
|
# ── Session Identity ───────────────────────────────────────────────────
|
|
15
19
|
|
|
16
20
|
# Validates a session identity string against the allowlist regex.
|
|
@@ -76,28 +80,6 @@ get_session_identity() {
|
|
|
76
80
|
printf '%s' "$identity"
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
# ── Input Sanitization ─────────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
# Sanitize a config value: strip shell-injection patterns (OWASP A03).
|
|
82
|
-
# Removes: backticks, $(...), semicolons, pipes, newlines.
|
|
83
|
-
# Usage: sanitized="$(sanitize_config_value "$raw")"
|
|
84
|
-
sanitize_config_value() {
|
|
85
|
-
local val="$1"
|
|
86
|
-
# Remove backtick command substitution
|
|
87
|
-
val="${val//\`/}"
|
|
88
|
-
# Remove $(...) command substitution patterns (loop handles nested)
|
|
89
|
-
val="$(printf '%s' "$val" | sed -e ':loop' -e 's/\$([^()]*)//g' -e 't loop')"
|
|
90
|
-
# Remove semicolons (command chaining)
|
|
91
|
-
val="${val//;/}"
|
|
92
|
-
# Remove pipes (command chaining)
|
|
93
|
-
val="${val//|/}"
|
|
94
|
-
# Collapse newlines to spaces
|
|
95
|
-
val="$(printf '%s' "$val" | tr '\n' ' ')"
|
|
96
|
-
# Trim leading/trailing whitespace
|
|
97
|
-
val="$(printf '%s' "$val" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
98
|
-
printf '%s' "$val"
|
|
99
|
-
}
|
|
100
|
-
|
|
101
83
|
# ── Config Reading ─────────────────────────────────────────────────────
|
|
102
84
|
|
|
103
85
|
# Read a key from .beads/config.json (JSON format).
|
|
@@ -277,25 +259,37 @@ _SYNC_STALENESS_THRESHOLD=900
|
|
|
277
259
|
# On success: records Unix epoch timestamp to .beads/.last-sync
|
|
278
260
|
# and updates file index from all in-progress issues' task files.
|
|
279
261
|
#
|
|
262
|
+
# _run_sync — execute beads sync (pull + push).
|
|
263
|
+
# If BD_SYNC_CMD is set, run it as a single command (for testing).
|
|
264
|
+
# Otherwise, run the default two-step pull+push sequence.
|
|
265
|
+
# shellcheck disable=SC2086
|
|
266
|
+
_run_sync() {
|
|
267
|
+
if [[ -n "${BD_SYNC_CMD:-}" ]]; then
|
|
268
|
+
$BD_SYNC_CMD
|
|
269
|
+
else
|
|
270
|
+
bd dolt pull && bd dolt push
|
|
271
|
+
fi
|
|
272
|
+
}
|
|
273
|
+
|
|
280
274
|
# Environment overrides (for testing):
|
|
281
|
-
# BD_SYNC_CMD — command to run instead of
|
|
275
|
+
# BD_SYNC_CMD — single command to run instead of default pull+push (e.g. "echo mock-sync")
|
|
282
276
|
# FILE_INDEX_ROOT — root directory for file-index.sh (default: repo_dir)
|
|
283
277
|
auto_sync() {
|
|
284
278
|
local repo_dir="${1:-.}"
|
|
285
|
-
local sync_cmd="${BD_SYNC_CMD:-bd sync}"
|
|
286
279
|
local last_sync_file="$repo_dir/.beads/.last-sync"
|
|
287
280
|
|
|
288
281
|
# Ensure .beads directory exists
|
|
289
282
|
mkdir -p "$repo_dir/.beads"
|
|
290
283
|
|
|
291
|
-
# Run
|
|
292
|
-
|
|
293
|
-
if $sync_cmd >/dev/null 2>&1; then
|
|
284
|
+
# Run sync — helper function avoids eval while supporting compound default
|
|
285
|
+
if _run_sync >/dev/null 2>&1; then
|
|
294
286
|
# Success: record timestamp
|
|
295
287
|
date +%s > "$last_sync_file"
|
|
296
288
|
|
|
297
289
|
# Update file index from in-progress issues' task files
|
|
298
|
-
_auto_sync_update_file_index "$repo_dir"
|
|
290
|
+
_auto_sync_update_file_index "$repo_dir" || {
|
|
291
|
+
echo "Warning: file index update failed after sync" >&2
|
|
292
|
+
}
|
|
299
293
|
else
|
|
300
294
|
# Failure: warn but continue (non-blocking)
|
|
301
295
|
local last_ts="unknown"
|
|
@@ -340,7 +334,8 @@ _auto_sync_update_file_index() {
|
|
|
340
334
|
|
|
341
335
|
# Check jq is available
|
|
342
336
|
if ! command -v jq &>/dev/null; then
|
|
343
|
-
|
|
337
|
+
echo "Warning: jq not found — file index update skipped" >&2
|
|
338
|
+
return 1
|
|
344
339
|
fi
|
|
345
340
|
|
|
346
341
|
# Extract in-progress issue IDs (LWW resolution: group by id, take last, filter in_progress)
|
package/scripts/test.js
CHANGED
|
@@ -22,7 +22,24 @@ function detectPackageManager() {
|
|
|
22
22
|
const pkgManager = detectPackageManager();
|
|
23
23
|
console.log(`🧪 Running test suite (${pkgManager} test)...`);
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
// Strip git hook environment variables so child processes (especially tests
|
|
26
|
+
// that create temp git repos) never accidentally operate on the real worktree.
|
|
27
|
+
// During pre-push hooks, git sets GIT_DIR pointing to the repo — any test that
|
|
28
|
+
// runs `git init` / `git commit` in a temp dir inherits this and silently
|
|
29
|
+
// commits into the worktree instead, creating rogue "initial commit" that
|
|
30
|
+
// deletes the entire codebase.
|
|
31
|
+
const env = { ...process.env };
|
|
32
|
+
for (const key of Object.keys(env)) {
|
|
33
|
+
if (key === 'GIT_DIR' || key === 'GIT_WORK_TREE' || key === 'GIT_INDEX_FILE'
|
|
34
|
+
|| key === 'GIT_OBJECT_DIRECTORY' || key === 'GIT_ALTERNATE_OBJECT_DIRECTORIES'
|
|
35
|
+
|| key === 'GIT_QUARANTINE_PATH') {
|
|
36
|
+
delete env[key];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Use 'run test' to invoke the package.json script (which may include --timeout flags)
|
|
41
|
+
// 'bun test' is a built-in that ignores package.json scripts
|
|
42
|
+
const result = spawnSync(pkgManager, ['run', 'test'], { stdio: 'inherit', shell: isWindows, env });
|
|
26
43
|
|
|
27
44
|
if (result.error) {
|
|
28
45
|
console.error('');
|