forge-workflow 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.cursorrules +149 -0
  2. package/bin/forge.js +43 -3
  3. package/lib/agents/README.md +46 -1
  4. package/lib/agents/cline.plugin.json +11 -4
  5. package/lib/agents/codex.plugin.json +2 -2
  6. package/lib/agents/copilot.plugin.json +5 -5
  7. package/lib/agents/cursor.plugin.json +1 -1
  8. package/lib/agents/kilocode.plugin.json +1 -1
  9. package/lib/agents/opencode.plugin.json +7 -4
  10. package/lib/agents/roo.plugin.json +10 -3
  11. package/lib/agents-config.js +127 -79
  12. package/lib/codex-skills.js +50 -0
  13. package/lib/commands/_issue.js +172 -0
  14. package/lib/commands/_registry.js +40 -1
  15. package/lib/commands/claim.js +5 -0
  16. package/lib/commands/close.js +5 -0
  17. package/lib/commands/commands-reset.js +147 -0
  18. package/lib/commands/create.js +5 -0
  19. package/lib/commands/dev.js +26 -0
  20. package/lib/commands/issue.js +5 -0
  21. package/lib/commands/list.js +5 -0
  22. package/lib/commands/plan.js +18 -0
  23. package/lib/commands/ready.js +5 -0
  24. package/lib/commands/setup.js +4295 -0
  25. package/lib/commands/ship.js +20 -0
  26. package/lib/commands/show.js +5 -0
  27. package/lib/commands/status.js +210 -44
  28. package/lib/commands/sync.js +19 -1
  29. package/lib/commands/update.js +5 -0
  30. package/lib/commands/validate.js +13 -0
  31. package/lib/detect-agent.js +38 -8
  32. package/lib/detection-utils.js +405 -0
  33. package/lib/file-utils.js +260 -0
  34. package/lib/forge-context.js +42 -0
  35. package/lib/frontmatter.js +79 -0
  36. package/lib/husky-migration.js +113 -12
  37. package/lib/lefthook-check.js +27 -6
  38. package/lib/plugin-manager.js +225 -72
  39. package/lib/project-discovery.js +39 -5
  40. package/lib/runtime-health.js +305 -0
  41. package/lib/shell-utils.js +50 -0
  42. package/lib/ui-utils.js +43 -0
  43. package/lib/validation-utils.js +163 -0
  44. package/lib/workflow/enforce-stage.js +179 -0
  45. package/lib/workflow/stages.js +201 -0
  46. package/lib/workflow/state.js +332 -0
  47. package/opencode.json +67 -0
  48. package/package.json +15 -5
  49. package/scripts/beads-context.sh +12 -4
  50. package/scripts/check-agents.js +103 -0
  51. package/scripts/lib/eval-runner.js +50 -0
  52. package/scripts/pr-coordinator.sh +71 -21
  53. package/scripts/smart-status.sh +21 -11
  54. package/scripts/sync-commands.js +49 -20
  55. package/scripts/test.js +16 -1
@@ -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
- const fs = require('fs');
297
- const path = require('path');
298
- const crypto = require('crypto');
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
- const manifestDir = path.join(repoRoot, '.forge');
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 = { parseFrontmatter, buildFile, AGENT_ADAPTERS, adaptForAgent, syncCommands };
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/test.js CHANGED
@@ -22,9 +22,24 @@ function detectPackageManager() {
22
22
  const pkgManager = detectPackageManager();
23
23
  console.log(`🧪 Running test suite (${pkgManager} test)...`);
24
24
 
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
+
25
40
  // Use 'run test' to invoke the package.json script (which may include --timeout flags)
26
41
  // 'bun test' is a built-in that ignores package.json scripts
27
- const result = spawnSync(pkgManager, ['run', 'test'], { stdio: 'inherit', shell: isWindows });
42
+ const result = spawnSync(pkgManager, ['run', 'test'], { stdio: 'inherit', shell: isWindows, env });
28
43
 
29
44
  if (result.error) {
30
45
  console.error('');