@giamme/forge 0.0.0-stage → 0.9.0

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 (152) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/LICENSE +21 -0
  3. package/README.md +194 -2
  4. package/SKILL.md +123 -0
  5. package/VERSION +1 -0
  6. package/docs/README.md +26 -0
  7. package/docs/add-ons.md +33 -0
  8. package/docs/artifacts.md +69 -0
  9. package/docs/commands.md +315 -0
  10. package/docs/dataset.md +20 -0
  11. package/docs/decomposed-runs.md +268 -0
  12. package/docs/development.md +95 -0
  13. package/docs/forge-ui.md +92 -0
  14. package/docs/fractal.md +181 -0
  15. package/docs/install.md +160 -0
  16. package/docs/memory.md +156 -0
  17. package/docs/models.md +118 -0
  18. package/docs/reviews.md +231 -0
  19. package/docs/safety.md +55 -0
  20. package/docs/troubleshooting.md +54 -0
  21. package/forge +79 -0
  22. package/package.json +27 -4
  23. package/references/dataset.md +329 -0
  24. package/references/decompose.md +667 -0
  25. package/references/fractal-dashboard.md +21 -0
  26. package/references/fractal.md +307 -0
  27. package/references/harnesses.md +365 -0
  28. package/references/jev.md +865 -0
  29. package/references/memory.md +228 -0
  30. package/references/planning.md +42 -0
  31. package/references/ripwire.md +47 -0
  32. package/references/runtime.md +64 -0
  33. package/references/solo.md +113 -0
  34. package/references/ui.md +112 -0
  35. package/registry.tsv +81 -0
  36. package/scripts/forge-artifact.sh +78 -0
  37. package/scripts/forge-contract.py +430 -0
  38. package/scripts/forge-dataset.py +6 -0
  39. package/scripts/forge-dispatch.sh +671 -0
  40. package/scripts/forge-fail-first.py +195 -0
  41. package/scripts/forge-fractal-dispatch.sh +4 -0
  42. package/scripts/forge-fractal-options.sh +54 -0
  43. package/scripts/forge-fractal.py +6 -0
  44. package/scripts/forge-guard.py +266 -0
  45. package/scripts/forge-install-fractal.sh +4 -0
  46. package/scripts/forge-install-ripwire.sh +52 -0
  47. package/scripts/forge-install.sh +135 -0
  48. package/scripts/forge-jev-options.sh +259 -0
  49. package/scripts/forge-jev.py +6 -0
  50. package/scripts/forge-lib-plans.sh +785 -0
  51. package/scripts/forge-lib-review.sh +494 -0
  52. package/scripts/forge-lib-task.sh +302 -0
  53. package/scripts/forge-lib-verify.sh +30 -0
  54. package/scripts/forge-memory.sh +575 -0
  55. package/scripts/forge-metrics.sh +39 -0
  56. package/scripts/forge-parallel-options.sh +76 -0
  57. package/scripts/forge-parallel.sh +1717 -0
  58. package/scripts/forge-plan-check.py +118 -0
  59. package/scripts/forge-prompt.py +45 -0
  60. package/scripts/forge-ripwire.py +136 -0
  61. package/scripts/forge-route.py +344 -0
  62. package/scripts/forge-runs.py +121 -0
  63. package/scripts/forge-runtime.py +586 -0
  64. package/scripts/forge-schedule.py +312 -0
  65. package/scripts/forge-shadow.py +305 -0
  66. package/scripts/forge-solo.sh +347 -0
  67. package/scripts/forge-ui-options.sh +42 -0
  68. package/scripts/forge-ui.py +435 -0
  69. package/scripts/forge-update.sh +71 -0
  70. package/scripts/forge-verify-coverage.py +956 -0
  71. package/scripts/forge-version.py +240 -0
  72. package/scripts/forge_dataset/__init__.py +439 -0
  73. package/scripts/forge_dataset/capture.py +330 -0
  74. package/scripts/forge_dataset/checks.py +109 -0
  75. package/scripts/forge_dataset/cli.py +233 -0
  76. package/scripts/forge_dataset/export.py +1009 -0
  77. package/scripts/forge_dataset/harvest.py +716 -0
  78. package/scripts/forge_dataset/importer.py +412 -0
  79. package/scripts/forge_dataset/outcomes.py +275 -0
  80. package/scripts/forge_dataset/routing.py +278 -0
  81. package/scripts/forge_fractal/__init__.py +84 -0
  82. package/scripts/forge_fractal/bridge.py +46 -0
  83. package/scripts/forge_fractal/cli.py +202 -0
  84. package/scripts/forge_fractal/dashboard.css +138 -0
  85. package/scripts/forge_fractal/dashboard.html +41 -0
  86. package/scripts/forge_fractal/dashboard.js +687 -0
  87. package/scripts/forge_fractal/decomposition.py +262 -0
  88. package/scripts/forge_fractal/execution.py +659 -0
  89. package/scripts/forge_fractal/inspection.py +261 -0
  90. package/scripts/forge_fractal/install.py +158 -0
  91. package/scripts/forge_fractal/invocation.py +94 -0
  92. package/scripts/forge_fractal/pipeline.py +124 -0
  93. package/scripts/forge_fractal/selection.py +179 -0
  94. package/scripts/forge_jev/__init__.py +376 -0
  95. package/scripts/forge_jev/backtest.py +322 -0
  96. package/scripts/forge_jev/cli.py +980 -0
  97. package/scripts/forge_jev/client.py +418 -0
  98. package/scripts/forge_jev/corpus.py +156 -0
  99. package/scripts/forge_jev/coupling.py +244 -0
  100. package/scripts/forge_jev/features.py +24 -0
  101. package/scripts/forge_jev/gates.py +100 -0
  102. package/scripts/forge_jev/memory.py +163 -0
  103. package/scripts/forge_jev/outcome.py +49 -0
  104. package/scripts/forge_jev/predict.py +204 -0
  105. package/scripts/forge_jev/questions.py +661 -0
  106. package/scripts/forge_jev/review.py +73 -0
  107. package/scripts/forge_jev/routing.py +271 -0
  108. package/scripts/forge_jev/subset.py +123 -0
  109. package/scripts/forge_jev/verify.py +453 -0
  110. package/scripts/forge_registry.py +140 -0
  111. package/ui/README.md +347 -0
  112. package/ui/THIRD_PARTY_NOTICES.md +177 -0
  113. package/ui/docs/api.md +970 -0
  114. package/ui/forge_ui/__init__.py +20 -0
  115. package/ui/forge_ui/__main__.py +135 -0
  116. package/ui/forge_ui/diffs.py +454 -0
  117. package/ui/forge_ui/executions.py +436 -0
  118. package/ui/forge_ui/export.py +214 -0
  119. package/ui/forge_ui/findings.py +132 -0
  120. package/ui/forge_ui/fractal.py +582 -0
  121. package/ui/forge_ui/models.py +216 -0
  122. package/ui/forge_ui/reader.py +1424 -0
  123. package/ui/forge_ui/server.py +312 -0
  124. package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
  125. package/ui/forge_ui/static/app.js +1079 -0
  126. package/ui/forge_ui/static/cloud3d.js +647 -0
  127. package/ui/forge_ui/static/cloudmodel.js +375 -0
  128. package/ui/forge_ui/static/cloudui.js +397 -0
  129. package/ui/forge_ui/static/convo.js +295 -0
  130. package/ui/forge_ui/static/datatable.js +232 -0
  131. package/ui/forge_ui/static/diffview.js +235 -0
  132. package/ui/forge_ui/static/favicon.svg +1 -0
  133. package/ui/forge_ui/static/files.js +776 -0
  134. package/ui/forge_ui/static/findings3d.js +134 -0
  135. package/ui/forge_ui/static/gantt.js +97 -0
  136. package/ui/forge_ui/static/graph.js +266 -0
  137. package/ui/forge_ui/static/icons.js +94 -0
  138. package/ui/forge_ui/static/index.html +67 -0
  139. package/ui/forge_ui/static/iso.js +816 -0
  140. package/ui/forge_ui/static/leaderboard.js +311 -0
  141. package/ui/forge_ui/static/map3d.js +45 -0
  142. package/ui/forge_ui/static/modelstats.js +249 -0
  143. package/ui/forge_ui/static/notify.js +179 -0
  144. package/ui/forge_ui/static/panel.js +562 -0
  145. package/ui/forge_ui/static/runmodel.js +667 -0
  146. package/ui/forge_ui/static/scene3d.js +1471 -0
  147. package/ui/forge_ui/static/search.js +120 -0
  148. package/ui/forge_ui/static/shell.js +137 -0
  149. package/ui/forge_ui/static/stagectl.js +150 -0
  150. package/ui/forge_ui/static/style.css +1073 -0
  151. package/ui/forge_ui/static/theme.js +25 -0
  152. package/ui/forge_ui/transcripts.py +608 -0
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env python3
2
+ """forge runs [--repo DIR | --all] [--limit N] [--json]: this repo's recent runs, in the terminal.
3
+
4
+ Every solo run, `plan` and `run` registers its run dir (forge_registry.py); this lists them newest
5
+ first, one line each (run id, kind, age, task states, verification and final review, goal) with the
6
+ run dir under it, so you can see what happened without a browser and get the dir to resume, retry
7
+ or open with `forge ui open`. The repo is the Git top level of the current directory unless
8
+ `--repo` names one; `--all` lists the runs of every repo. It reads the same run summaries as
9
+ forge-ui and never writes anything.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import json
15
+ import os
16
+ from pathlib import Path
17
+ import subprocess
18
+ import sys
19
+ import time
20
+
21
+ SKILL = Path(__file__).resolve().parent.parent
22
+ sys.path.insert(0, str(SKILL / 'scripts'))
23
+ sys.path.insert(0, str(SKILL / 'ui'))
24
+
25
+ import forge_registry # noqa: E402
26
+ from forge_ui import reader # noqa: E402
27
+
28
+ FIELDS = ('key', 'run_id', 'kind', 'goal', 'repo', 'path', 'created', 'updated', 'task_count', 'counts',
29
+ 'verification_status', 'final_review')
30
+
31
+
32
+ def repo_of(start: Path) -> Path:
33
+ """The Git top level of `start`, else `start` itself: the key runs are registered under."""
34
+ try:
35
+ out = subprocess.run(['git', '-C', str(start), 'rev-parse', '--show-toplevel'], capture_output=True,
36
+ text=True, stdin=subprocess.DEVNULL, timeout=10)
37
+ if out.returncode == 0 and out.stdout.strip():
38
+ return Path(out.stdout.strip()).resolve()
39
+ except (OSError, subprocess.SubprocessError):
40
+ pass
41
+ return start.resolve()
42
+
43
+
44
+ def registered(path: Path) -> list[str]:
45
+ """The run dirs one registry file lists (absolute paths, comments skipped)."""
46
+ try:
47
+ lines = path.read_text(encoding='utf-8', errors='replace').splitlines()
48
+ except OSError:
49
+ return []
50
+ return [line.strip() for line in lines if line.strip() and not line.startswith('#') and os.path.isabs(line.strip())]
51
+
52
+
53
+ def runs(repo: Path | None) -> list[dict]:
54
+ if repo is None:
55
+ found = reader.discover(registry=True, registry_dir=forge_registry.registry_dir())
56
+ else:
57
+ found = reader.discover(plans=registered(forge_registry.runs_registry(repo)), registry=False)
58
+ result = []
59
+ for path, source in found:
60
+ try:
61
+ summary = reader.summarize(path, source)
62
+ except Exception: # one unreadable run dir must not hide the others
63
+ continue
64
+ result.append({k: summary.get(k) for k in FIELDS})
65
+ return sorted(result, key=lambda r: r['updated'] if r['updated'] is not None else float('-inf'), reverse=True)
66
+
67
+
68
+ def age(epoch, now) -> str:
69
+ if epoch is None:
70
+ return '?'
71
+ s = max(0, now - epoch)
72
+ for unit, size in (('d', 86400), ('h', 3600), ('m', 60)):
73
+ if s >= size:
74
+ return f'{int(s // size)}{unit} ago'
75
+ return 'just now'
76
+
77
+
78
+ def line(run: dict, now: float, with_repo: bool = False) -> str:
79
+ counts = ' · '.join(f'{status.lower()} {n}' for status, n in (run['counts'] or {}).items()) or 'no tasks'
80
+ checks = ''.join(f' {label} {run[key]}' for label, key in (('verify', 'verification_status'), ('review', 'final_review'))
81
+ if run.get(key))
82
+ goal = str(run['goal'] or 'Untitled run').splitlines()[0]
83
+ if len(goal) > 72:
84
+ goal = goal[:71] + '…'
85
+ repo = f" {Path(run['repo']).name or run['repo']}" if with_repo and run.get('repo') else ''
86
+ return f"{run['run_id'] or run['key']} {run['kind']}{repo} {age(run['updated'], now)} {counts}{checks} {goal}\n {run['path']}"
87
+
88
+
89
+ def main(argv=None) -> int:
90
+ parser = argparse.ArgumentParser(prog='forge runs', description="This repo's recent forge runs, newest first. Read-only.")
91
+ which = parser.add_mutually_exclusive_group()
92
+ which.add_argument('--repo', type=Path, metavar='DIR', help='the repo whose runs to list (default: the Git top level of the current directory)')
93
+ which.add_argument('--all', action='store_true', help='list the runs of every repo')
94
+ parser.add_argument('--limit', type=int, default=10, metavar='N', help='at most N runs (default 10; 0 lists them all)')
95
+ parser.add_argument('--json', action='store_true', help='print the runs as a JSON array, for scripts and agents')
96
+ args = parser.parse_args(argv)
97
+ if args.limit < 0:
98
+ parser.error('--limit must be 0 or more')
99
+ if args.repo is not None and not args.repo.is_dir():
100
+ parser.error(f'--repo: no such directory: {args.repo}')
101
+ repo = None if args.all else repo_of(args.repo if args.repo is not None else Path.cwd())
102
+ found = runs(repo)
103
+ shown = found[:args.limit] if args.limit else found
104
+ if args.json:
105
+ json.dump(shown, sys.stdout, indent=2)
106
+ sys.stdout.write('\n')
107
+ return 0
108
+ where = 'every repo' if repo is None else str(repo)
109
+ if not found:
110
+ print(f'forge runs: no runs registered for {where}')
111
+ print('Runs register when they start (/forge "<goal>" --dwarf <model>); FORGE_RUN_REGISTRY=off turns that off.')
112
+ return 0
113
+ print(f'forge runs for {where}: {len(shown)} of {len(found)}, newest first')
114
+ now = time.time()
115
+ for run in shown:
116
+ print(line(run, now, with_repo=repo is None))
117
+ return 0
118
+
119
+
120
+ if __name__ == '__main__':
121
+ sys.exit(main())