@hassangameryt/freeflow 0.8.0 → 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.
- package/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +96 -149
- package/capabilities/cognitive-routing/SKILL.md +21 -129
- package/capabilities/cognitive-routing/references/coordinator.md +163 -0
- package/capabilities/cognitive-routing/references/worker.md +38 -0
- package/capabilities/compaction/SKILL.md +3 -3
- package/capabilities/compaction/references/summary-format.md +26 -15
- package/capabilities/messaging/SKILL.md +49 -0
- package/capabilities/task-memory/SKILL.md +87 -0
- package/command-surface.json +6 -54
- package/gemini-extension.json +1 -1
- package/hooks/claude/hooks.json +13 -3
- package/hooks/codex/hooks.json +2 -1
- package/hooks/shared/runtime-context.mjs +69 -77
- package/package.json +25 -8
- package/pi-extension/dist/ask-user/ask.js +108 -0
- package/pi-extension/dist/ask-user/form.js +250 -0
- package/pi-extension/dist/ask-user/questions.js +66 -0
- package/pi-extension/dist/ask-user/tool.js +124 -0
- package/pi-extension/dist/cognitive-routing/assembler.js +35 -11
- package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
- package/pi-extension/dist/cognitive-routing/config.js +3 -4
- package/pi-extension/dist/cognitive-routing/event-store.js +4 -1
- package/pi-extension/dist/cognitive-routing/gate.js +20 -5
- package/pi-extension/dist/cognitive-routing/handlers.js +15 -13
- package/pi-extension/dist/cognitive-routing/model-control.js +17 -8
- package/pi-extension/dist/cognitive-routing/projection.js +23 -6
- package/pi-extension/dist/cognitive-routing/provenance.js +3 -1
- package/pi-extension/dist/cognitive-routing/render.js +15 -15
- package/pi-extension/dist/cognitive-routing/runtime.js +4 -2
- package/pi-extension/dist/cognitive-routing/schemas.js +4 -4
- package/pi-extension/dist/cognitive-routing/session.js +3 -1
- package/pi-extension/dist/cognitive-routing/sources.js +29 -4
- package/pi-extension/dist/cognitive-routing/state.js +18 -8
- package/pi-extension/dist/cognitive-routing/status.js +4 -3
- package/pi-extension/dist/cognitive-routing/tools.js +29 -15
- package/pi-extension/dist/cognitive-routing/types.js +10 -1
- package/pi-extension/dist/compaction/carry.js +36 -22
- package/pi-extension/dist/compaction/config.js +4 -2
- package/pi-extension/dist/compaction/controller.js +52 -29
- package/pi-extension/dist/compaction/harness.js +19 -3
- package/pi-extension/dist/compaction/results.js +14 -13
- package/pi-extension/dist/compaction/tool.js +6 -6
- package/pi-extension/dist/host/catalog.js +20 -35
- package/pi-extension/dist/host/config-validation.js +233 -0
- package/pi-extension/dist/host/config.js +78 -91
- package/pi-extension/dist/host/inbound.js +142 -0
- package/pi-extension/dist/host/projection-tags.js +1 -1
- package/pi-extension/dist/host/prompts.js +24 -13
- package/pi-extension/dist/host/read-only-session.js +6 -1
- package/pi-extension/dist/host/request-history.js +7 -3
- package/pi-extension/dist/host/rows.js +25 -0
- package/pi-extension/dist/host/runtime-state.js +17 -2
- package/pi-extension/dist/host/section-keeper.js +85 -0
- package/pi-extension/dist/host/settings/freeflow-command.js +846 -93
- package/pi-extension/dist/host/settings/settings-view.js +27 -8
- package/pi-extension/dist/host/status.js +19 -5
- package/pi-extension/dist/index.js +638 -72
- package/pi-extension/dist/messaging/config.js +27 -0
- package/pi-extension/dist/messaging/messaging.js +398 -0
- package/pi-extension/dist/messaging/registry.js +57 -0
- package/pi-extension/dist/messaging/render.js +90 -0
- package/pi-extension/dist/messaging/tools.js +109 -0
- package/pi-extension/dist/messaging/transport.js +101 -0
- package/pi-extension/dist/provider-support/cache/keepalive.js +16 -4
- package/pi-extension/dist/task-memory/config.js +49 -0
- package/pi-extension/dist/task-memory/registry.js +119 -0
- package/pi-extension/dist/task-memory/render.js +107 -0
- package/pi-extension/dist/task-memory/runtime.js +994 -0
- package/pi-extension/dist/task-memory/tool.js +233 -0
- package/pi-extension/dist/tool-execution/apply-patch/tool.js +1 -1
- package/pi-extension/dist/tool-execution/background.js +220 -28
- package/pi-extension/dist/tool-execution/config.js +14 -3
- package/pi-extension/dist/tool-execution/file-tracking.js +24 -4
- package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
- package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
- package/pi-extension/dist/tool-execution/tools.js +2 -2
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/runtime/prompts/core.md +24 -14
- package/runtime/prompts/interaction-contract.md +7 -4
- package/runtime/prompts/tool-execution.md +3 -16
- package/runtime/prompts/working-method.md +16 -99
- package/skills/deliver-work/SKILL.md +76 -0
- package/skills/deliver-work/references/branch-closeout.md +134 -0
- package/skills/deliver-work/references/commit.md +83 -0
- package/skills/deliver-work/references/launch.md +94 -0
- package/skills/deliver-work/references/release.md +85 -0
- package/skills/deliver-work/scripts/commit-state.mjs +139 -0
- package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
- package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
- package/skills/deliver-work/scripts/push-route.mjs +122 -0
- package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
- package/skills/discuss-work/SKILL.md +148 -0
- package/skills/{discuss → discuss-work}/references/discussion-continuity.md +10 -27
- package/skills/discuss-work/references/learning-action.md +36 -0
- package/skills/discuss-work/scripts/choices.mjs +101 -0
- package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
- package/skills/evaluate-behavior/SKILL.md +107 -0
- package/skills/evaluate-behavior/references/authoring.md +381 -0
- package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
- package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
- package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
- package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
- package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
- package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
- package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
- package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
- package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
- package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
- package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
- package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
- package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
- package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
- package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
- package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
- package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
- package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
- package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
- package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
- package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
- package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
- package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
- package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
- package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
- package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
- package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
- package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
- package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
- package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +119 -14
- package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
- package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
- package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
- package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
- package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
- package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
- package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +235 -265
- package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
- package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
- package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
- package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +159 -18
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-command-tool.mjs +21 -6
- package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
- package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
- package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
- package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
- package/skills/execute-work/SKILL.md +96 -104
- package/skills/execute-work/references/code-practices.md +27 -51
- package/skills/execute-work/references/design.md +271 -0
- package/skills/execute-work/references/diagnose.md +117 -0
- package/skills/execute-work/references/migration.md +108 -0
- package/skills/execute-work/references/simplify.md +65 -0
- package/skills/execute-work/scripts/self-review.mjs +422 -0
- package/skills/execute-work/scripts/touch-map.mjs +290 -0
- package/skills/review-work/SKILL.md +89 -65
- package/skills/review-work/references/adjudication.md +94 -0
- package/skills/review-work/references/artifact.md +198 -0
- package/skills/review-work/references/evals-and-judges.md +66 -0
- package/skills/review-work/references/work.md +177 -0
- package/skills/review-work/scripts/check-brief.mjs +128 -0
- package/skills/review-work/scripts/review-packet.mjs +233 -0
- package/skills/setup-freeflow/SKILL.md +68 -17
- package/skills/track-work/SKILL.md +52 -20
- package/skills/track-work/references/handoff.md +190 -0
- package/skills/track-work/references/projects.md +51 -0
- package/skills/track-work/references/working-record-format.md +36 -18
- package/skills/track-work/scripts/lib/document.mjs +17 -13
- package/skills/track-work/scripts/lib/format.mjs +2 -0
- package/skills/track-work/scripts/lib/recover.mjs +254 -0
- package/skills/track-work/scripts/lib/registry.mjs +656 -0
- package/skills/track-work/scripts/lib/search.mjs +40 -0
- package/skills/track-work/scripts/lib/store.mjs +46 -15
- package/skills/track-work/scripts/lib/views.mjs +50 -1
- package/skills/track-work/scripts/working-record.mjs +232 -13
- package/skills/verify-work/SKILL.md +45 -37
- package/skills/verify-work/references/feature-map.md +120 -0
- package/skills/verify-work/references/runtime-evidence.md +203 -0
- package/skills/verify-work/references/test-design.md +25 -6
- package/skills/verify-work/scripts/test-changes.mjs +246 -0
- package/skills/write-docs/SKILL.md +113 -25
- package/skills/write-docs/references/documentation-shapes.md +68 -3
- package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
- package/skills/write-docs/scripts/artifact-check.mjs +148 -0
- package/skills/write-docs/scripts/docs-check.mjs +96 -6
- package/skills/write-docs/scripts/plan-lint.mjs +123 -0
- package/skills/write-skill/SKILL.md +92 -52
- package/skills/write-skill/references/activation-and-wording.md +143 -0
- package/skills/write-skill/references/placement-and-dependencies.md +154 -0
- package/skills/write-skill/scripts/lib/compare.mjs +701 -0
- package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
- package/skills/write-skill/scripts/skill-author.mjs +51 -8
- package/capabilities/cognitive-routing/references/both-mode.md +0 -36
- package/capabilities/cognitive-routing/references/executor-mode.md +0 -48
- package/capabilities/cognitive-routing/references/helper-mode.md +0 -47
- package/plugin.json +0 -33
- package/skills/action-selection/SKILL.md +0 -92
- package/skills/action-selection/references/trajectory-stalls.md +0 -73
- package/skills/bypass/SKILL.md +0 -153
- package/skills/commit-work/SKILL.md +0 -93
- package/skills/commit-work/references/git-state-edges.md +0 -94
- package/skills/commit-work/references/simple-push.md +0 -94
- package/skills/commit-work/references/staging-decisions.md +0 -106
- package/skills/decision-gate/SKILL.md +0 -87
- package/skills/design-for-depth/SKILL.md +0 -108
- package/skills/design-for-depth/references/design-pressure-signals.md +0 -84
- package/skills/design-for-depth/references/implementation-decisions.md +0 -76
- package/skills/design-for-depth/references/interface-design-loop.md +0 -105
- package/skills/design-for-depth/references/module-and-dependency-design.md +0 -117
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -125
- package/skills/diagnose-failure/SKILL.md +0 -140
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -50
- package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
- package/skills/discuss/SKILL.md +0 -140
- package/skills/evaluate-skill/SKILL.md +0 -188
- package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -415
- package/skills/evaluate-skill/references/evaluation-design.md +0 -112
- package/skills/evaluate-skill/references/execution-and-evidence.md +0 -111
- package/skills/evaluate-skill/references/review-and-revision.md +0 -94
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -448
- package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
- package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
- package/skills/evaluate-skill/scripts/lib/review.mjs +0 -196
- package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -405
- package/skills/evaluate-skill/scripts/lib/trials.mjs +0 -103
- package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -181
- package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -182
- package/skills/execute-work/references/execute-work-edges.md +0 -78
- package/skills/finish-branch/SKILL.md +0 -117
- package/skills/finish-branch/references/integration-options.md +0 -80
- package/skills/handoff/SKILL.md +0 -82
- package/skills/handoff/references/handoff-templates.md +0 -150
- package/skills/launch-work/SKILL.md +0 -116
- package/skills/launch-work/references/launch-readiness.md +0 -85
- package/skills/migration-work/SKILL.md +0 -115
- package/skills/migration-work/references/migration-lifecycle.md +0 -82
- package/skills/release-work/SKILL.md +0 -118
- package/skills/release-work/references/release-evidence.md +0 -78
- package/skills/review-artifact/SKILL.md +0 -143
- package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
- package/skills/review-work/references/adjudicate-work-review.md +0 -97
- package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
- package/skills/review-work/references/security-risk-lens.md +0 -67
- package/skills/setup-freeflow/references/activation-contract.md +0 -103
- package/skills/setup-freeflow/references/host-setup.md +0 -67
- package/skills/simplify-code/SKILL.md +0 -85
- package/skills/simplify-code/references/simplification-patterns.md +0 -70
- package/skills/verify-work/references/browser-runtime-evidence.md +0 -51
- package/skills/verify-work/references/integration-evidence.md +0 -49
- package/skills/verify-work/references/performance-evidence.md +0 -59
- package/skills/workflow/SKILL.md +0 -171
- package/skills/workflow/references/domain-skill-composition.md +0 -45
- package/skills/write-plan/SKILL.md +0 -92
- package/skills/write-plan/references/plan-shapes.md +0 -156
- package/skills/write-skill/references/activation-boundaries.md +0 -106
- package/skills/write-skill/references/agent-first-instructions.md +0 -133
- package/skills/write-skill/references/progressive-disclosure.md +0 -129
- package/skills/write-skill/references/skill-dependency-graphs.md +0 -192
- package/skills/write-spec/SKILL.md +0 -85
- package/skills/write-spec/references/artifact-standards.md +0 -86
- package/skills/write-spec/references/decision-records.md +0 -80
- package/skills/write-spec/references/spec-shapes.md +0 -143
- /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
- /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Puts the facts a self-review needs in one scratch report, so the agent reads its actual change rather than its
|
|
3
|
+
// memory of it: Git status, the diff stat against a base, every changed existing test, fixture, snapshot or shared
|
|
4
|
+
// test setup with its hunk (Verify Work's test-changes.mjs), each new test and whether it fails on the base, added lines
|
|
5
|
+
// that look like leftover debug output, per requirement the last recorded check and whether it ran after the last
|
|
6
|
+
// edit, and the feature files (Verify Work's feature map) that cover a changed file, each with whether it was driven
|
|
7
|
+
// live since that code last changed (Write Docs' docs-check --covering --driven). It lists; judging each item is the
|
|
8
|
+
// agent's and the user's.
|
|
9
|
+
//
|
|
10
|
+
// A check is recorded with `check`, which runs it and stamps it with the state it ran on: HEAD plus a hash of every
|
|
11
|
+
// uncommitted change, untracked files included. A requirement's check "ran after the last edit" when its stamp equals
|
|
12
|
+
// the current state's.
|
|
13
|
+
//
|
|
14
|
+
// Usage: self-review.mjs --base <ref> [--root <repository>] [--requirements <file>] [--test-command <command>]
|
|
15
|
+
// [--scratch <dir>] [--json]
|
|
16
|
+
// self-review.mjs check --id <R1> [--root <repository>] [--scratch <dir>] (--command <shell command> | -- <argv>)
|
|
17
|
+
// --requirements: a file whose lines start with a requirement ID (R1, - R2, ...), optionally ending in
|
|
18
|
+
// "check: <command>". --test-command runs each new test on the base in a temporary worktree; "{file}" in it is replaced
|
|
19
|
+
// by the test's path, otherwise the path is appended. --scratch defaults to a folder under the system temporary
|
|
20
|
+
// directory; pass the task's scratch/ when it has a Working Record. Exit code 0 when the report is written, 2 on a
|
|
21
|
+
// usage or Git error; `check` exits with the command's exit code.
|
|
22
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
23
|
+
import { createHash } from "node:crypto";
|
|
24
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, appendFileSync, symlinkSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { tmpdir } from "node:os";
|
|
26
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { parseArgs } from "node:util";
|
|
29
|
+
|
|
30
|
+
const USAGE = [
|
|
31
|
+
"Usage: self-review.mjs --base <ref> [--root <repository>] [--requirements <file>] [--test-command <command>] [--scratch <dir>] [--json]",
|
|
32
|
+
" self-review.mjs check --id <R1> [--root <repository>] [--scratch <dir>] (--command <shell command> | -- <argv>)",
|
|
33
|
+
].join("\n");
|
|
34
|
+
const TEST_CHANGES = resolve(dirname(fileURLToPath(import.meta.url)), "../../verify-work/scripts/test-changes.mjs");
|
|
35
|
+
const DOCS_CHECK = resolve(dirname(fileURLToPath(import.meta.url)), "../../write-docs/scripts/docs-check.mjs");
|
|
36
|
+
const CAP = 40;
|
|
37
|
+
const LEFTOVER =
|
|
38
|
+
/\b(console\.(log|debug|trace)|debugger|print\(|println!|dbg!|fmt\.Print|var_dump|binding\.pry|breakpoint\(\)|pdb\.set_trace)|\b(TODO|FIXME|XXX)\b/;
|
|
39
|
+
|
|
40
|
+
function fail(message) {
|
|
41
|
+
console.error(message);
|
|
42
|
+
process.exit(2);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const argv = process.argv.slice(2);
|
|
46
|
+
const mode = argv[0] === "check" ? "check" : "report";
|
|
47
|
+
const dash = argv.indexOf("--");
|
|
48
|
+
const own = mode === "check" ? argv.slice(1, dash === -1 ? undefined : dash) : argv;
|
|
49
|
+
let values;
|
|
50
|
+
try {
|
|
51
|
+
({ values } = parseArgs({
|
|
52
|
+
args: own,
|
|
53
|
+
options: {
|
|
54
|
+
base: { type: "string" },
|
|
55
|
+
root: { type: "string" },
|
|
56
|
+
requirements: { type: "string" },
|
|
57
|
+
"test-command": { type: "string" },
|
|
58
|
+
scratch: { type: "string" },
|
|
59
|
+
id: { type: "string" },
|
|
60
|
+
command: { type: "string" },
|
|
61
|
+
json: { type: "boolean", default: false },
|
|
62
|
+
},
|
|
63
|
+
}));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
fail(`${error.message}\n${USAGE}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const git = (cwd, args, options = {}) =>
|
|
69
|
+
execFileSync("git", args, {
|
|
70
|
+
cwd,
|
|
71
|
+
encoding: "utf8",
|
|
72
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
73
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
74
|
+
...options,
|
|
75
|
+
});
|
|
76
|
+
const gitOrFail = (cwd, args, what) => {
|
|
77
|
+
try {
|
|
78
|
+
return git(cwd, args);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
fail(`${what}: ${String(error.stderr || error.message).trim()}`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const root = resolve(
|
|
85
|
+
gitOrFail(resolve(values.root ?? "."), ["rev-parse", "--show-toplevel"], "not inside a Git repository").trim(),
|
|
86
|
+
);
|
|
87
|
+
const scratch = resolve(
|
|
88
|
+
values.scratch ??
|
|
89
|
+
join(
|
|
90
|
+
tmpdir(),
|
|
91
|
+
"freeflow-self-review",
|
|
92
|
+
`${basename(root)}-${createHash("sha256").update(root).digest("hex").slice(0, 8)}`,
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
mkdirSync(scratch, { recursive: true });
|
|
96
|
+
const logPath = join(scratch, "self-review-checks.jsonl");
|
|
97
|
+
const head = () => {
|
|
98
|
+
try {
|
|
99
|
+
return git(root, ["rev-parse", "--verify", "--quiet", "HEAD"]).trim();
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// The state a check observed: HEAD, every uncommitted change to tracked files, and every untracked file's content.
|
|
106
|
+
// Files under the scratch folder are left out, so recording a check does not change the state it recorded.
|
|
107
|
+
function stamp() {
|
|
108
|
+
const hash = createHash("sha256");
|
|
109
|
+
const commit = head();
|
|
110
|
+
hash.update(`${commit}\n`);
|
|
111
|
+
if (commit) hash.update(git(root, ["-c", "core.quotePath=false", "diff", "--binary", "--no-ext-diff", "HEAD"]));
|
|
112
|
+
const inScratch = (path) => !relative(scratch, join(root, path)).startsWith("..");
|
|
113
|
+
for (const path of git(root, ["ls-files", "-z", "--others", "--exclude-standard"]).split("\0").filter(Boolean)) {
|
|
114
|
+
if (inScratch(path)) continue;
|
|
115
|
+
hash.update(`\0${path}\0`);
|
|
116
|
+
try {
|
|
117
|
+
hash.update(readFileSync(join(root, path)));
|
|
118
|
+
} catch {
|
|
119
|
+
hash.update("unreadable");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return hash.digest("hex").slice(0, 16);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const readLog = () =>
|
|
126
|
+
existsSync(logPath)
|
|
127
|
+
? readFileSync(logPath, "utf8")
|
|
128
|
+
.split("\n")
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
.flatMap((line) => {
|
|
131
|
+
try {
|
|
132
|
+
return [JSON.parse(line)];
|
|
133
|
+
} catch {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
: [];
|
|
138
|
+
|
|
139
|
+
if (mode === "check") {
|
|
140
|
+
if (!values.id) fail(`check needs --id\n${USAGE}`);
|
|
141
|
+
const words = dash === -1 ? [] : argv.slice(dash + 1);
|
|
142
|
+
if (!values.command && !words.length) fail(`check needs --command or a command after --\n${USAGE}`);
|
|
143
|
+
const before = stamp();
|
|
144
|
+
const result = values.command
|
|
145
|
+
? spawnSync(values.command, { cwd: root, shell: true, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 })
|
|
146
|
+
: spawnSync(words[0], words.slice(1), { cwd: root, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
147
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}${result.error ? String(result.error.message) : ""}`;
|
|
148
|
+
const exit = result.status ?? 1;
|
|
149
|
+
// A check that edits files (a formatter, a snapshot update) observed the state before its edits only.
|
|
150
|
+
const after = stamp();
|
|
151
|
+
const entry = {
|
|
152
|
+
id: values.id,
|
|
153
|
+
command: values.command ?? words.join(" "),
|
|
154
|
+
exit,
|
|
155
|
+
at: new Date().toISOString(),
|
|
156
|
+
head: head(),
|
|
157
|
+
stamp: before,
|
|
158
|
+
changedState: after !== before,
|
|
159
|
+
tail: output.trimEnd().split("\n").slice(-20),
|
|
160
|
+
};
|
|
161
|
+
appendFileSync(logPath, `${JSON.stringify(entry)}\n`);
|
|
162
|
+
process.stdout.write(output);
|
|
163
|
+
console.log(
|
|
164
|
+
`\n${values.id}: exit ${exit}; recorded in ${logPath}${entry.changedState ? " (the command changed files: rerun it to check the new state)" : ""}`,
|
|
165
|
+
);
|
|
166
|
+
process.exit(exit);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!values.base) fail(USAGE);
|
|
170
|
+
try {
|
|
171
|
+
git(root, ["rev-parse", "--verify", "--quiet", `${values.base}^{commit}`]);
|
|
172
|
+
} catch {
|
|
173
|
+
fail(`no such commit: ${values.base}`);
|
|
174
|
+
}
|
|
175
|
+
const baseCommit = git(root, ["rev-parse", `${values.base}^{commit}`]).trim();
|
|
176
|
+
|
|
177
|
+
const capped = (lines, cap = CAP) => ({ lines: lines.slice(0, cap), cut: Math.max(0, lines.length - cap) });
|
|
178
|
+
const status = capped(git(root, ["status", "--short", "--branch"]).trimEnd().split("\n").filter(Boolean));
|
|
179
|
+
const stat = git(root, ["-c", "core.quotePath=false", "diff", "--stat=120", "--no-color", "--no-ext-diff", baseCommit])
|
|
180
|
+
.trimEnd()
|
|
181
|
+
.split("\n")
|
|
182
|
+
.filter(Boolean);
|
|
183
|
+
const untracked = git(root, ["ls-files", "-z", "--others", "--exclude-standard"])
|
|
184
|
+
.split("\0")
|
|
185
|
+
.filter(Boolean)
|
|
186
|
+
.filter((path) => relative(scratch, join(root, path)).startsWith(".."));
|
|
187
|
+
|
|
188
|
+
// Feature files covering a changed path: documents with a "Driving it" section, the Write Docs feature file shape.
|
|
189
|
+
// A file is "stale" when its covered code changed after its Verified at, even with its text edited alongside.
|
|
190
|
+
const featureFiles = (() => {
|
|
191
|
+
const changed = [
|
|
192
|
+
...git(root, ["-c", "core.quotePath=false", "diff", "--name-only", baseCommit]).split("\n").filter(Boolean),
|
|
193
|
+
...untracked,
|
|
194
|
+
];
|
|
195
|
+
if (!changed.length) return [];
|
|
196
|
+
const result = spawnSync(
|
|
197
|
+
process.execPath,
|
|
198
|
+
[DOCS_CHECK, ...changed.flatMap((path) => ["--covering", path]), "--driven", "--root", root, "--json"],
|
|
199
|
+
{ cwd: root, encoding: "utf8" },
|
|
200
|
+
);
|
|
201
|
+
if (result.status !== 0) return [];
|
|
202
|
+
const found = new Map();
|
|
203
|
+
for (const { path, documents } of JSON.parse(result.stdout).paths)
|
|
204
|
+
for (const doc of documents) {
|
|
205
|
+
let text = "";
|
|
206
|
+
try {
|
|
207
|
+
text = readFileSync(join(root, doc.path), "utf8");
|
|
208
|
+
} catch {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (!/^## Driving it\b/m.test(text)) continue;
|
|
212
|
+
const entry = found.get(doc.path) ?? { path: doc.path, verifiedAt: doc.verifiedAt, state: doc.state, covers: [] };
|
|
213
|
+
entry.covers.push(path);
|
|
214
|
+
found.set(doc.path, entry);
|
|
215
|
+
}
|
|
216
|
+
return [...found.values()];
|
|
217
|
+
})();
|
|
218
|
+
|
|
219
|
+
const testChanges = (extra) => {
|
|
220
|
+
const result = spawnSync(process.execPath, [TEST_CHANGES, "--base", baseCommit, "--root", root, "--json", ...extra], {
|
|
221
|
+
encoding: "utf8",
|
|
222
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
223
|
+
});
|
|
224
|
+
if (result.status !== 0) fail(`test-changes failed: ${(result.stderr || result.stdout).trim()}`);
|
|
225
|
+
return JSON.parse(result.stdout);
|
|
226
|
+
};
|
|
227
|
+
const changedTests = testChanges([]).changes.map((change) => {
|
|
228
|
+
const hunk = change.hunks ? change.hunks.split("\n") : [];
|
|
229
|
+
return { ...change, hunks: hunk.slice(0, 80).join("\n"), hunkLinesCut: Math.max(0, hunk.length - 80) };
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// New tests, each run on the base in a temporary worktree: a new test that passes there may not test the change.
|
|
233
|
+
const newTests = testChanges(["--new"]).tests.map((each) => ({ ...each, onBase: null }));
|
|
234
|
+
const testCommand = values["test-command"];
|
|
235
|
+
if (testCommand && newTests.some((each) => each.kind === "test")) {
|
|
236
|
+
const where = join(scratch, `base-${process.pid}-${Date.now()}`);
|
|
237
|
+
gitOrFail(root, ["worktree", "add", "--detach", "--quiet", where, baseCommit], "git worktree add failed");
|
|
238
|
+
try {
|
|
239
|
+
if (existsSync(join(root, "node_modules")) && !existsSync(join(where, "node_modules")))
|
|
240
|
+
symlinkSync(join(root, "node_modules"), join(where, "node_modules"), "dir");
|
|
241
|
+
// Every new file goes in, so a new test's new fixture is there too.
|
|
242
|
+
for (const each of newTests) {
|
|
243
|
+
mkdirSync(dirname(join(where, each.path)), { recursive: true });
|
|
244
|
+
copyFileSync(join(root, each.path), join(where, each.path));
|
|
245
|
+
}
|
|
246
|
+
for (const each of newTests.filter((test) => test.kind === "test").slice(0, 10)) {
|
|
247
|
+
const command = testCommand.includes("{file}")
|
|
248
|
+
? testCommand.replaceAll("{file}", each.path)
|
|
249
|
+
: `${testCommand} ${each.path}`;
|
|
250
|
+
const run = spawnSync(command, { cwd: where, shell: true, encoding: "utf8", timeout: 300_000 });
|
|
251
|
+
const tail = `${run.stdout ?? ""}${run.stderr ?? ""}`.trimEnd().split("\n").slice(-5);
|
|
252
|
+
each.onBase = {
|
|
253
|
+
command,
|
|
254
|
+
exit: run.status,
|
|
255
|
+
result: run.status === 0 ? "passes on the base: it may not test the change" : "fails on the base",
|
|
256
|
+
tail,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
} finally {
|
|
260
|
+
try {
|
|
261
|
+
git(root, ["worktree", "remove", "--force", where]);
|
|
262
|
+
} catch {
|
|
263
|
+
// Reported below: the agent removes it by hand.
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (existsSync(where)) newTests.push({ status: "note", path: where, kind: "leftover worktree: remove it" });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Requirements and their last recorded checks.
|
|
270
|
+
const current = stamp();
|
|
271
|
+
const log = readLog();
|
|
272
|
+
let requirements = [];
|
|
273
|
+
if (values.requirements) {
|
|
274
|
+
const file = resolve(values.requirements);
|
|
275
|
+
if (!existsSync(file)) fail(`no such requirements file: ${values.requirements}`);
|
|
276
|
+
requirements = readFileSync(file, "utf8")
|
|
277
|
+
.split("\n")
|
|
278
|
+
.flatMap((line) => {
|
|
279
|
+
const match = /^\s*(?:[-*]\s+)?\**(R\d+)\**[:.)\]]?\s+(.*)$/.exec(line);
|
|
280
|
+
if (!match) return [];
|
|
281
|
+
const check = /\bcheck:\s*`?([^`]+?)`?\s*$/i.exec(match[2]);
|
|
282
|
+
const text = (check ? match[2].slice(0, check.index) : match[2]).replace(/[\s—–-]+$/, "").trim();
|
|
283
|
+
return [{ id: match[1], text, check: check?.[1] ?? null }];
|
|
284
|
+
})
|
|
285
|
+
.map((requirement) => {
|
|
286
|
+
const last = log.filter((entry) => entry.id === requirement.id).at(-1) ?? null;
|
|
287
|
+
const afterLastEdit = last ? last.stamp === current && !last.changedState : false;
|
|
288
|
+
return {
|
|
289
|
+
...requirement,
|
|
290
|
+
lastCheck: last && { command: last.command, exit: last.exit, at: last.at, head: last.head },
|
|
291
|
+
afterLastEdit,
|
|
292
|
+
state: !last
|
|
293
|
+
? "no check recorded"
|
|
294
|
+
: afterLastEdit
|
|
295
|
+
? last.exit === 0
|
|
296
|
+
? "passed on the current state"
|
|
297
|
+
: `failed on the current state (exit ${last.exit})`
|
|
298
|
+
: "stale: the change differs from the state it checked; rerun it",
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Added lines that look like leftover debug output or notes, in tracked files and untracked ones.
|
|
304
|
+
const leftovers = [];
|
|
305
|
+
let file = null;
|
|
306
|
+
let line = 0;
|
|
307
|
+
for (const text of git(root, [
|
|
308
|
+
"-c",
|
|
309
|
+
"core.quotePath=false",
|
|
310
|
+
"diff",
|
|
311
|
+
"--no-color",
|
|
312
|
+
"--no-ext-diff",
|
|
313
|
+
"--unified=0",
|
|
314
|
+
baseCommit,
|
|
315
|
+
]).split("\n")) {
|
|
316
|
+
const header = /^\+\+\+ b\/(.*)$/.exec(text);
|
|
317
|
+
if (header) file = header[1];
|
|
318
|
+
const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)/.exec(text);
|
|
319
|
+
if (hunk) line = Number(hunk[1]);
|
|
320
|
+
else if (text.startsWith("+") && !text.startsWith("+++")) {
|
|
321
|
+
if (LEFTOVER.test(text)) leftovers.push({ at: `${file}:${line}`, text: text.slice(1).trim().slice(0, 160) });
|
|
322
|
+
line += 1;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
for (const path of untracked) {
|
|
326
|
+
let contents;
|
|
327
|
+
try {
|
|
328
|
+
contents = readFileSync(join(root, path), "utf8");
|
|
329
|
+
} catch {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (contents.includes("\0")) continue;
|
|
333
|
+
contents.split("\n").forEach((text, index) => {
|
|
334
|
+
if (LEFTOVER.test(text)) leftovers.push({ at: `${path}:${index + 1}`, text: text.trim().slice(0, 160) });
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const report = {
|
|
339
|
+
command: "self-review",
|
|
340
|
+
root,
|
|
341
|
+
base: { ref: values.base, commit: baseCommit },
|
|
342
|
+
head: head(),
|
|
343
|
+
stamp: current,
|
|
344
|
+
status: status.lines,
|
|
345
|
+
statusCut: status.cut,
|
|
346
|
+
diffStat: stat,
|
|
347
|
+
untracked,
|
|
348
|
+
changedTests,
|
|
349
|
+
newTests,
|
|
350
|
+
requirements,
|
|
351
|
+
featureFiles,
|
|
352
|
+
leftovers: leftovers.slice(0, 20),
|
|
353
|
+
leftoversCut: Math.max(0, leftovers.length - 20),
|
|
354
|
+
notChecked: [
|
|
355
|
+
"whether each change is justified: judge every changed test and fixture with the user",
|
|
356
|
+
"callers of what changed: rerun the queries from Find what the change touches (touch-map.mjs)",
|
|
357
|
+
...(testCommand ? [] : ["new tests on the base: pass --test-command to see each one fail there"]),
|
|
358
|
+
...(values.requirements ? [] : ["requirements: pass --requirements with one line per requirement"]),
|
|
359
|
+
],
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const md = [`# Self-review: ${values.base} (${baseCommit.slice(0, 12)}) -> working tree`, ""];
|
|
363
|
+
md.push("## Status", "", "```text", ...status.lines, "```");
|
|
364
|
+
if (status.cut) md.push(`(${status.cut} more lines cut)`);
|
|
365
|
+
md.push("", "## Diff stat against the base", "", "```text", ...(stat.length ? stat : ["no tracked changes"]), "```");
|
|
366
|
+
if (untracked.length)
|
|
367
|
+
md.push(`Untracked: ${untracked.slice(0, CAP).join(", ")}${untracked.length > CAP ? ", ..." : ""}`);
|
|
368
|
+
md.push("", `## Changed existing tests, fixtures and setup (${changedTests.length})`, "");
|
|
369
|
+
if (!changedTests.length) md.push("None.");
|
|
370
|
+
for (const change of changedTests) {
|
|
371
|
+
md.push(
|
|
372
|
+
`### ${change.status} ${change.kind} ${change.to ? `${change.path} -> ${change.to}` : change.path} (+${change.added} -${change.removed})`,
|
|
373
|
+
);
|
|
374
|
+
if (change.hunks) md.push("", "```diff", change.hunks, "```");
|
|
375
|
+
if (change.hunkLinesCut) md.push(`(${change.hunkLinesCut} more diff lines cut)`);
|
|
376
|
+
md.push("");
|
|
377
|
+
}
|
|
378
|
+
md.push("", `## New tests (${newTests.length})`, "");
|
|
379
|
+
if (!newTests.length) md.push("None.");
|
|
380
|
+
for (const each of newTests)
|
|
381
|
+
md.push(
|
|
382
|
+
`- ${each.kind} ${each.path}${each.onBase ? `: ${each.onBase.result} (\`${each.onBase.command}\` exit ${each.onBase.exit})` : each.kind === "test" ? ": not run on the base" : ""}`,
|
|
383
|
+
);
|
|
384
|
+
md.push("", `## Requirements (${requirements.length})`, "");
|
|
385
|
+
if (!values.requirements) md.push("No requirements file given.");
|
|
386
|
+
for (const requirement of requirements) {
|
|
387
|
+
md.push(`- ${requirement.id} ${requirement.text} — ${requirement.state}`);
|
|
388
|
+
if (requirement.lastCheck)
|
|
389
|
+
md.push(
|
|
390
|
+
` - last check: \`${requirement.lastCheck.command}\` exit ${requirement.lastCheck.exit} at ${requirement.lastCheck.at}`,
|
|
391
|
+
);
|
|
392
|
+
else if (requirement.check)
|
|
393
|
+
md.push(` - record one: self-review.mjs check --id ${requirement.id} --command "${requirement.check}"`);
|
|
394
|
+
}
|
|
395
|
+
md.push("", `## Feature files covering the change (${featureFiles.length})`, "");
|
|
396
|
+
if (!featureFiles.length) md.push("None found.");
|
|
397
|
+
else
|
|
398
|
+
md.push(
|
|
399
|
+
"A claim about what a user sees here is checked by driving these files' steps (Verify Work's Project Recipe). Stale: covered code changed since the file was last driven.",
|
|
400
|
+
"",
|
|
401
|
+
);
|
|
402
|
+
for (const each of featureFiles)
|
|
403
|
+
md.push(`- ${each.path}: ${each.state}, verified at ${each.verifiedAt ?? "none"} (covers ${each.covers.join(", ")})`);
|
|
404
|
+
md.push("", `## Possible leftovers (${leftovers.length})`, "");
|
|
405
|
+
if (!leftovers.length) md.push("None found.");
|
|
406
|
+
for (const each of report.leftovers) md.push(`- ${each.at}: ${each.text}`);
|
|
407
|
+
if (report.leftoversCut) md.push(`(${report.leftoversCut} more cut)`);
|
|
408
|
+
md.push("", "## Not checked by this report", "", ...report.notChecked.map((item) => `- ${item}`), "");
|
|
409
|
+
const reportPath = join(scratch, "self-review.md");
|
|
410
|
+
writeFileSync(reportPath, md.join("\n"));
|
|
411
|
+
report.reportPath = reportPath;
|
|
412
|
+
|
|
413
|
+
if (values.json) console.log(JSON.stringify(report, null, 2));
|
|
414
|
+
else {
|
|
415
|
+
const stale = requirements.filter((each) => !each.afterLastEdit).length;
|
|
416
|
+
const passingOnBase = newTests.filter((each) => each.onBase?.exit === 0).length;
|
|
417
|
+
console.log(`Self-review report: ${reportPath}`);
|
|
418
|
+
console.log(
|
|
419
|
+
`${stat.length ? stat.at(-1).trim() : "no tracked changes"}; ${untracked.length} untracked; ${changedTests.length} changed existing test file(s); ${newTests.length} new; ${passingOnBase} new test(s) pass on the base; ${requirements.length} requirement(s), ${stale} without a check after the last edit; ${featureFiles.length} feature file(s) cover the change, ${featureFiles.filter((each) => each.state !== "current").length} not driven since; ${leftovers.length} possible leftover(s).`,
|
|
420
|
+
);
|
|
421
|
+
console.log("Read the report before you report the change.");
|
|
422
|
+
}
|