@hecer/yoke 0.2.0 → 0.8.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 (84) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +583 -494
  3. package/bench/.runs/claude-2026-07-09T22-34-01/.yoke/config.yaml +6 -0
  4. package/bench/.runs/claude-2026-07-09T22-34-01/.yoke/context/DECISIONS.md +9 -0
  5. package/bench/.runs/claude-2026-07-09T22-34-01/.yoke/prd.yaml +38 -0
  6. package/bench/.runs/claude-2026-07-09T22-34-01/bench-verify.mjs +15 -0
  7. package/bench/.runs/claude-2026-07-09T22-34-01/package.json +9 -0
  8. package/bench/.runs/claude-2026-07-09T22-34-01/src/index.mjs +48 -0
  9. package/bench/.runs/claude-2026-07-09T22-34-01/tests/STORY-1.test.mjs +24 -0
  10. package/bench/.runs/claude-2026-07-09T22-34-01/tests/STORY-2.test.mjs +28 -0
  11. package/bench/.runs/claude-2026-07-09T22-34-01/tests/STORY-3.test.mjs +25 -0
  12. package/bench/.runs/gemini-2026-07-09T22-34-02/.yoke/config.yaml +6 -0
  13. package/bench/.runs/gemini-2026-07-09T22-34-02/.yoke/prd.yaml +32 -0
  14. package/bench/.runs/gemini-2026-07-09T22-34-02/bench-verify.mjs +15 -0
  15. package/bench/.runs/gemini-2026-07-09T22-34-02/package.json +9 -0
  16. package/bench/.runs/gemini-2026-07-09T22-34-02/src/index.mjs +3 -0
  17. package/bench/.runs/gemini-2026-07-09T22-34-02/tests/STORY-1.test.mjs +24 -0
  18. package/bench/.runs/gemini-2026-07-09T22-34-02/tests/STORY-2.test.mjs +28 -0
  19. package/bench/.runs/gemini-2026-07-09T22-34-02/tests/STORY-3.test.mjs +25 -0
  20. package/bench/README.md +42 -0
  21. package/bench/RESULTS.md +36 -0
  22. package/bench/fixtures/string-kit/.yoke/config.yaml +6 -0
  23. package/bench/fixtures/string-kit/.yoke/prd.yaml +32 -0
  24. package/bench/fixtures/string-kit/bench-verify.mjs +15 -0
  25. package/bench/fixtures/string-kit/package.json +9 -0
  26. package/bench/fixtures/string-kit/src/index.mjs +3 -0
  27. package/bench/fixtures/string-kit/tests/STORY-1.test.mjs +24 -0
  28. package/bench/fixtures/string-kit/tests/STORY-2.test.mjs +28 -0
  29. package/bench/fixtures/string-kit/tests/STORY-3.test.mjs +25 -0
  30. package/bench/results/claude-2026-07-09T22-34-01.json +40 -0
  31. package/bench/run.mjs +115 -0
  32. package/canon/loop/loop-spec.md +5 -1
  33. package/canon/manifest.yaml +3 -0
  34. package/canon/skills/yoke-retrofit/SKILL.md +1 -0
  35. package/canon/tools/claude-mem.md +15 -0
  36. package/canon/tools/ui-ux-pro-max.md +15 -0
  37. package/dist/cli.js +37 -6
  38. package/dist/loop/cleanup.js +44 -3
  39. package/dist/loop/loop.js +67 -2
  40. package/dist/loop/reporter.js +88 -4
  41. package/dist/loop/run-command.js +25 -8
  42. package/dist/loop/runner.js +133 -14
  43. package/dist/loop/watchdog.js +67 -17
  44. package/dist/retrofit/apply.js +10 -3
  45. package/dist/retrofit/config.js +9 -1
  46. package/dist/retrofit/gitignore.js +4 -0
  47. package/dist/retrofit/planners/claude.js +4 -1
  48. package/dist/retrofit/planners/gemini.js +2 -1
  49. package/dist/retrofit/preserve.js +51 -0
  50. package/dist/update/check.js +86 -0
  51. package/dist/update/refresh.js +28 -0
  52. package/dist/update/upgrade.js +30 -0
  53. package/docs/PUBLISHING.md +41 -0
  54. package/docs/superpowers/plans/2026-06-27-forge-a-canon.md +815 -0
  55. package/docs/superpowers/plans/2026-06-27-forge-b1-retrofit-claude.md +842 -0
  56. package/docs/superpowers/plans/2026-06-27-forge-b2-codex-gemini-tools.md +884 -0
  57. package/docs/superpowers/plans/2026-06-27-forge-c1-loop-engine.md +891 -0
  58. package/docs/superpowers/plans/2026-06-28-baustein-e-context-layer.md +981 -0
  59. package/docs/superpowers/plans/2026-06-28-forge-b3-settings-merge.md +364 -0
  60. package/docs/superpowers/plans/2026-06-28-forge-c2-loop-verify.md +502 -0
  61. package/docs/superpowers/plans/2026-06-28-forge-c3-multi-agent-runners.md +377 -0
  62. package/docs/superpowers/plans/2026-06-28-forge-c4-worktree-isolation.md +413 -0
  63. package/docs/superpowers/plans/2026-06-28-forge-c5-review-iteration.md +419 -0
  64. package/docs/superpowers/plans/2026-06-28-forge-cleanup-dep0190.md +233 -0
  65. package/docs/superpowers/plans/2026-06-28-forge-d-codegraph-choice-minimal-code.md +523 -0
  66. package/docs/superpowers/plans/2026-06-29-baustein-f-routing.md +258 -0
  67. package/docs/superpowers/plans/2026-06-29-baustein-g-loop-observability.md +1006 -0
  68. package/docs/superpowers/plans/2026-06-29-baustein-h-loop-robustness.md +374 -0
  69. package/docs/superpowers/plans/2026-06-30-baustein-i-visual-design-verification.md +450 -0
  70. package/docs/superpowers/plans/2026-07-02-baustein-j-cross-model-review-gstack-compose.md +645 -0
  71. package/docs/superpowers/plans/2026-07-02-baustein-k-zero-to-100-bootstrap.md +1024 -0
  72. package/docs/superpowers/plans/2026-07-02-baustein-m-flow-smoke-proofs.md +574 -0
  73. package/docs/superpowers/specs/2026-06-27-forge-cross-agent-harness-design.md +144 -0
  74. package/docs/superpowers/specs/2026-06-28-baustein-e-context-layer-design.md +146 -0
  75. package/docs/superpowers/specs/2026-06-29-baustein-f-routing-design.md +106 -0
  76. package/docs/superpowers/specs/2026-06-29-baustein-g-loop-observability-design.md +186 -0
  77. package/docs/superpowers/specs/2026-06-29-baustein-h-loop-robustness-design.md +113 -0
  78. package/docs/superpowers/specs/2026-06-30-baustein-i-visual-design-verification-design.md +98 -0
  79. package/docs/superpowers/specs/2026-07-02-baustein-j-cross-model-review-gstack-compose-design.md +137 -0
  80. package/docs/superpowers/specs/2026-07-02-baustein-k-zero-to-100-bootstrap-design.md +200 -0
  81. package/docs/superpowers/specs/2026-07-02-baustein-m-flow-smoke-proofs-design.md +155 -0
  82. package/docs/superpowers/specs/2026-07-10-companion-tools-decision.md +33 -0
  83. package/docs/superpowers/specs/2026-07-10-multi-agent-parallel-loop-design.md +99 -0
  84. package/package.json +8 -2
@@ -0,0 +1,86 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { spawn } from 'node:child_process';
5
+ import { fileURLToPath } from 'node:url';
6
+ export const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
7
+ // Machine-global (not per-project): one cache under the user's home dir.
8
+ export function cachePath() {
9
+ return join(homedir(), '.yoke', 'update-check.json');
10
+ }
11
+ export function readCache(file = cachePath()) {
12
+ if (!existsSync(file))
13
+ return null;
14
+ try {
15
+ const raw = JSON.parse(readFileSync(file, 'utf8'));
16
+ if (typeof raw.checkedAt === 'string' && typeof raw.latest === 'string') {
17
+ return { checkedAt: raw.checkedAt, latest: raw.latest };
18
+ }
19
+ }
20
+ catch { /* malformed — treat as absent */ }
21
+ return null;
22
+ }
23
+ export function writeCache(cache, file = cachePath()) {
24
+ mkdirSync(dirname(file), { recursive: true });
25
+ writeFileSync(file, JSON.stringify(cache));
26
+ }
27
+ // Strict numeric triple compare; prerelease suffixes are stripped (a prerelease
28
+ // of the same triple is not "newer"). Malformed input is never newer.
29
+ export function isNewer(candidate, current) {
30
+ const triple = (v) => {
31
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
32
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
33
+ };
34
+ const a = triple(candidate);
35
+ const b = triple(current);
36
+ if (!a || !b)
37
+ return false;
38
+ for (let i = 0; i < 3; i++) {
39
+ if (a[i] !== b[i])
40
+ return a[i] > b[i];
41
+ }
42
+ return false;
43
+ }
44
+ export function isStale(cache, now) {
45
+ if (!cache)
46
+ return true;
47
+ const t = Date.parse(cache.checkedAt);
48
+ if (!Number.isFinite(t))
49
+ return true;
50
+ return now.getTime() - t > CHECK_INTERVAL_MS;
51
+ }
52
+ export function currentYokeVersion() {
53
+ // src/update/ and dist/update/ both sit two levels below the package root.
54
+ const pkg = JSON.parse(readFileSync(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8'));
55
+ return pkg.version;
56
+ }
57
+ function spawnDetachedRefresh() {
58
+ const refresh = fileURLToPath(new URL('./refresh.js', import.meta.url));
59
+ try {
60
+ spawn(process.execPath, [refresh], { detached: true, stdio: 'ignore' }).unref();
61
+ }
62
+ catch { /* best-effort — next invocation retries */ }
63
+ }
64
+ // Print (to stderr) a one-line hint when a newer version is cached; kick off a
65
+ // detached cache refresh when stale. Silent in CI, --json runs, non-TTY pipes,
66
+ // and under YOKE_NO_UPDATE_CHECK=1. Never throws, never blocks.
67
+ export function maybeNotifyUpdate(currentVersion, opts = {}) {
68
+ const env = opts.env ?? process.env;
69
+ if (env.YOKE_NO_UPDATE_CHECK || env.CI)
70
+ return;
71
+ const argv = opts.argv ?? process.argv;
72
+ if (argv.includes('--json'))
73
+ return;
74
+ const tty = opts.tty ?? process.stderr.isTTY === true;
75
+ if (!tty)
76
+ return;
77
+ const cache = readCache(opts.cacheFile);
78
+ if (isStale(cache, opts.now ?? new Date())) {
79
+ (opts.spawnRefresh ?? spawnDetachedRefresh)();
80
+ return;
81
+ }
82
+ if (cache && isNewer(cache.latest, currentVersion)) {
83
+ const print = opts.print ?? ((m) => console.error(m));
84
+ print(`yoke ${currentVersion} → ${cache.latest} available — run: yoke upgrade (disable this check: YOKE_NO_UPDATE_CHECK=1)`);
85
+ }
86
+ }
@@ -0,0 +1,28 @@
1
+ import { pathToFileURL } from 'node:url';
2
+ import { writeCache } from './check.js';
3
+ // Detached background refresher (spawned by maybeNotifyUpdate when the cache
4
+ // is stale): one small registry request, write the cache, exit. Failures are
5
+ // silent by design — the notifier simply retries on a later invocation.
6
+ const REGISTRY_LATEST = 'https://registry.npmjs.org/@hecer%2fyoke/latest';
7
+ export async function refreshOnce(opts = {}) {
8
+ const fetchFn = opts.fetchFn ?? fetch;
9
+ const ctrl = new AbortController();
10
+ const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 5000);
11
+ try {
12
+ const res = await fetchFn(REGISTRY_LATEST, { signal: ctrl.signal });
13
+ if (!res.ok)
14
+ return;
15
+ const body = await res.json();
16
+ if (typeof body.version === 'string') {
17
+ writeCache({ checkedAt: (opts.now ?? new Date()).toISOString(), latest: body.version }, opts.cacheFile);
18
+ }
19
+ }
20
+ catch { /* offline / blocked / registry down — silent */ }
21
+ finally {
22
+ clearTimeout(timer);
23
+ }
24
+ }
25
+ const isMain = process.argv[1] ? pathToFileURL(process.argv[1]).href === import.meta.url : false;
26
+ if (isMain) {
27
+ refreshOnce().then(() => process.exit(0));
28
+ }
@@ -0,0 +1,30 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { readCache, isNewer, currentYokeVersion } from './check.js';
3
+ // `yoke upgrade` — the explicit path to the latest release.
4
+ export function runUpgrade(opts = {}) {
5
+ const exec = opts.exec ?? ((cmd) => { execSync(cmd, { stdio: 'inherit' }); });
6
+ console.log('Upgrading @hecer/yoke to latest...');
7
+ try {
8
+ exec('npm install -g @hecer/yoke@latest');
9
+ }
10
+ catch (e) {
11
+ console.error(`Upgrade failed: ${e.message}`);
12
+ console.error('Running from a git clone? Upgrade with: git pull && npm install && npm run build');
13
+ return 1;
14
+ }
15
+ console.log('✓ upgraded — the new version is active on the next yoke invocation.');
16
+ return 0;
17
+ }
18
+ // Opt-in auto-upgrade (`update.auto: true` in .yoke/config.yaml), evaluated at
19
+ // loop START only — never mid-run: the current process keeps executing the
20
+ // version it started with; the upgrade takes effect on the next invocation.
21
+ export function maybeAutoUpgrade(auto, opts = {}) {
22
+ if (auto !== true)
23
+ return;
24
+ const cache = readCache(opts.cacheFile);
25
+ const current = currentYokeVersion();
26
+ if (cache && isNewer(cache.latest, current)) {
27
+ console.error(`update.auto: upgrading yoke ${current} → ${cache.latest} (this run continues on ${current}; the upgrade applies from the next run)`);
28
+ runUpgrade(opts);
29
+ }
30
+ }
@@ -0,0 +1,41 @@
1
+ # Publishing channels — status & playbook
2
+
3
+ Where Yoke is published, and how each channel gets updated. (Researched 2026-07-10.)
4
+
5
+ ## Live
6
+
7
+ | Channel | How | Update path |
8
+ |---|---|---|
9
+ | **npm** — [`@hecer/yoke`](https://www.npmjs.com/package/@hecer/yoke) | `npm publish` (2FA) | every release |
10
+ | **GitHub** — [HECer/yoke](https://github.com/HECer/yoke) | push + tag | every release |
11
+ | **Claude Code plugin (self-marketplace)** | `.claude-plugin/plugin.json` + `marketplace.json` in this repo; users: `/plugin marketplace add HECer/yoke` → `/plugin install yoke@yoke` | bump `version` in `plugin.json` |
12
+ | **Gemini CLI extension** | `gemini-extension.json` + `GEMINI-EXTENSION.md` at repo root; users: `gemini extensions install https://github.com/HECer/yoke` | bump `version` in the manifest |
13
+
14
+ ## Submitted / pending
15
+
16
+ | Channel | How | Status |
17
+ |---|---|---|
18
+ | **Gemini extensions gallery** (geminicli.com/extensions) | automatic daily crawl: needs `gemini-extension.json` at repo root + `gemini-cli-extension` repo topic — both done | wait for crawler |
19
+ | **Anthropic community plugin directory** (`claude-community`, surfaced in `/plugin > Discover`) | form at **platform.claude.com/plugins/submit** (Console account, Developer role; submit the public repo URL; `claude plugin validate` runs in their pipeline — passes locally). After approval: pinned to a commit SHA, CI auto-bumps on push, catalog syncs nightly | **needs a human login** — see below |
20
+
21
+ ### Anthropic directory submission (manual step)
22
+
23
+ 1. Log in at https://platform.claude.com (free Console account is enough; role Developer+).
24
+ 2. Open https://platform.claude.com/plugins/submit
25
+ 3. Submit the public repo: `https://github.com/HECer/yoke`
26
+ 4. Suggested description: *"Cross-agent coding harness: one curated skill canon (TDD,
27
+ brainstorming → spec → plan, systematic debugging, cross-model review, design
28
+ verification) plus mechanical safety gates and an autonomous loop via the yoke CLI."*
29
+ 5. Category: development. Plugin name (immutable): `yoke`.
30
+
31
+ ## Worth doing later (community lists, PR/issue-based)
32
+
33
+ - **awesome-claude-code** (hesreallyhim) — issue-form only, explicitly human-submitted, no PRs.
34
+ - **ComposioHQ/awesome-claude-plugins** — PR per template (high merge latency).
35
+ - **davila7/claude-code-templates** (aitmpl.com) — PR per CONTRIBUTING.md.
36
+ - **Codex plugin directory** (platform.openai.com/plugins) — requires verified developer
37
+ identity + test cases; medium-high effort. Codex CLI users can already consume the repo
38
+ marketplace directly.
39
+ - Auto-crawled directories (crossaitools.com etc.) pick the repo up on their own once the
40
+ marketplace manifest exists.
41
+ - Launch channels (Product Hunt, Show HN, r/ClaudeAI, r/ClaudeCode) — deliberate, human-led.