merge e353f90d-7afb-43ff-b1f7-a520298e5fe5

240fce0gl0w

all commits

Message

merge e353f90d-7afb-43ff-b1f7-a520298e5fe5

Diff

.forge/ci/steps/02_budget.sh+2 −2
diff --git a/.forge/ci/steps/02_budget.sh b/.forge/ci/steps/02_budget.shindex ad9afec..7c22c1f 100755--- a/.forge/ci/steps/02_budget.sh+++ b/.forge/ci/steps/02_budget.sh@@ -1,6 +1,6 @@11 #!/bin/sh22 # 02_budget: byte-count hard budgets (PLAN M2).3-# root help <=300B/12 lines; --skill <=900B/30 lines; each <cmd> -h <=250B/8 lines.3+# root help <=300B/12 lines; --skill <=1000B/30 lines; each <cmd> -h <=250B/8 lines.44 set -e55 export CARGO_BUILD_JOBS=266 fail() { echo "budget fail: $1" >&2; exit 1; }@@ -12,7 +12,7 @@ check() {1212 bytes=$(f0rge | wc -c); lines=$(f0rge | wc -l)1313 check "root help" "$bytes" "$lines" 300 121414 bytes=$(f0rge --skill | wc -c); lines=$(f0rge --skill | wc -l)15-check "--skill" "$bytes" "$lines" 900 3015+check "--skill" "$bytes" "$lines" 1000 301616 for sub in issue pr task label milestone ci wiki release merge init skill; do1717   bytes=$(f0rge "$sub" -h | wc -c); lines=$(f0rge "$sub" -h | wc -l)1818   check "$sub -h" "$bytes" "$lines" 250 8
.forge/wiki/docs/PLAN.md+1 −1
diff --git a/.forge/wiki/docs/PLAN.md b/.forge/wiki/docs/PLAN.mdindex 1876cf3..ead11c4 100644--- a/.forge/wiki/docs/PLAN.md+++ b/.forge/wiki/docs/PLAN.md@@ -177,7 +177,7 @@ f0rge177177 |------------------------|---------------------------------|178178 | `f0rge` (root help)    | ≤ 12 lines, ≤ 300 bytes         |179179 | any subcommand `-h`    | ≤ 8 lines, ≤ 250 bytes          |180-| `f0rge --skill`        | ≤ 30 lines, ≤ 900 bytes         |180+| `f0rge --skill`        | ≤ 30 lines, ≤ 1000 bytes        |181181 | `list --json`          | 1 compact line per entity, no header/boilerplate |182182 | error                 | 1 line + exit code              |183183 | empty result          | `[]` or `{}` — no prose         |
crates/cli/src/cmd/init.rs+1 −1
diff --git a/crates/cli/src/cmd/init.rs b/crates/cli/src/cmd/init.rsindex 0d2b13d..dcb4a0d 100644--- a/crates/cli/src/cmd/init.rs+++ b/crates/cli/src/cmd/init.rs@@ -3,7 +3,7 @@33 use crate::CliError;44 55 pub fn help() -> &'static str {6-    "f0rge init: bootstrap\nusage: f0rge init [dir]\ncreates .forge/ skeleton; idempotent\ncontract: f0rge --skill\n"6+    "f0rge init: bootstrap\nusage: f0rge init [dir]\ncreates .forge/ skeleton; idempotent\ndocs: spec/schema.md §Init\ncontract: f0rge --skill\n"77 }88 99 /// Bootstrap skeleton. Nothing else. Existing .forge kept.
crates/cli/src/cmd/label.rs+1 −1
diff --git a/crates/cli/src/cmd/label.rs b/crates/cli/src/cmd/label.rsindex 328f4ae..05916b3 100644--- a/crates/cli/src/cmd/label.rs+++ b/crates/cli/src/cmd/label.rs@@ -6,7 +6,7 @@ use crate::CliError;66 use f0rge_core::{Forge, Label};77 88 pub fn help() -> &'static str {9-    "f0rge label: definitions\nusage: f0rge label [--json] <list|create|edit>\nfile: .forge/labels.md\ncontract: f0rge --skill\n"9+    "f0rge label: definitions\nusage: f0rge label <list|create|edit> [--json]\nfile: .forge/labels.md\ndocs: spec/schema.md §Label\ncontract: f0rge --skill\n"1010 }1111 1212 const VALS: &[&str] = &["name", "color", "description", "desc", "jq"];
crates/cli/src/cmd/merge.rs+1 −1
diff --git a/crates/cli/src/cmd/merge.rs b/crates/cli/src/cmd/merge.rsindex 06e2cd6..e92aef6 100644--- a/crates/cli/src/cmd/merge.rs+++ b/crates/cli/src/cmd/merge.rs@@ -11,7 +11,7 @@ use std::process::Command;1111 use std::sync::atomic::{AtomicU64, Ordering};1212 1313 pub fn help() -> &'static str {14-    "f0rge merge: imports\nusage: f0rge merge [--json] <folder|zip|branch-name>\nconflicts stop; agent resolves\ncontract: f0rge --skill\n"14+    "f0rge merge: imports\nusage: f0rge merge <folder|zip|branch-name> [--json]\ngate: scratch worktree; never touches main/branches; conflicts stop; you integrate (ff) + delete\ndocs: spec/schema.md §Merge-record\ncontract: f0rge --skill\n"1515 }1616 1717 static CTR: AtomicU64 = AtomicU64::new(0);
crates/cli/src/cmd/milestone.rs+1 −1
diff --git a/crates/cli/src/cmd/milestone.rs b/crates/cli/src/cmd/milestone.rsindex e964fb2..f7baedc 100644--- a/crates/cli/src/cmd/milestone.rs+++ b/crates/cli/src/cmd/milestone.rs@@ -6,7 +6,7 @@ use crate::CliError;66 use f0rge_core::{Forge, Milestone};77 88 pub fn help() -> &'static str {9-    "f0rge milestone: spans\nusage: f0rge milestone [--json] <list|create|edit>\nfile: .forge/milestones.md\ncontract: f0rge --skill\n"9+    "f0rge milestone: spans\nusage: f0rge milestone <list|create|edit> [--json]\nfile: .forge/milestones.md\ndocs: spec/schema.md §Milestone\ncontract: f0rge --skill\n"1010 }1111 1212 const VALS: &[&str] = &["name", "state", "due", "description", "desc", "jq"];
crates/cli/src/cmd/mod.rs+1 −1
diff --git a/crates/cli/src/cmd/mod.rs b/crates/cli/src/cmd/mod.rsindex 0e4afca..72f3f2c 100644--- a/crates/cli/src/cmd/mod.rs+++ b/crates/cli/src/cmd/mod.rs@@ -21,7 +21,7 @@ use crate::CliError;2121 /// Root help: <=12 lines, <=300 bytes. Whole surface from here.2222 /// Command map only; contract lives in `f0rge --skill`.2323 pub const ROOT_HELP: &str = "f0rge: local forge\n\24-usage: f0rge [--json|--jq E|--skill] <cmd> [args]\n\24+usage: f0rge [--skill] <cmd> [--json|--jq E] [args]\n\2525 issue pr task label milestone ci wiki\n\2626 release merge init skill\n\2727 'cmd -h' for help\n\
crates/cli/src/cmd/pr.rs+1 −1
diff --git a/crates/cli/src/cmd/pr.rs b/crates/cli/src/cmd/pr.rsindex 180765b..12a7e44 100644--- a/crates/cli/src/cmd/pr.rs+++ b/crates/cli/src/cmd/pr.rs@@ -12,7 +12,7 @@ use std::process::Command;1212 use std::sync::atomic::{AtomicU64, Ordering};1313 1414 pub fn help() -> &'static str {15-    "f0rge pr: pull requests\nusage: f0rge pr [--json] <create|diff|review|comment|merge|close|list>\nverbs: create diff review comment merge close list\ncontract: f0rge --skill\n"15+    "f0rge pr: pull requests\nusage: f0rge pr <create|diff|review|comment|merge|close|list> [--json]\nverbs: create diff review comment merge close list\ndocs: spec/schema.md §Pr\ncontract: f0rge --skill\n"1616 }1717 1818 static TMP_CTR: AtomicU64 = AtomicU64::new(0);
crates/cli/src/cmd/release.rs+1 −1
diff --git a/crates/cli/src/cmd/release.rs b/crates/cli/src/cmd/release.rsindex 1264fe2..1b2c98e 100644--- a/crates/cli/src/cmd/release.rs+++ b/crates/cli/src/cmd/release.rs@@ -12,7 +12,7 @@ use std::process::Command;1212 use std::sync::atomic::{AtomicU64, Ordering};1313 1414 pub fn help() -> &'static str {15-    "f0rge release: versions\nusage: f0rge release <create|list|view> [ver]\nnotes: .forge/releases/<ver>.md\ncontract: f0rge --skill\n"15+    "f0rge release: versions\nusage: f0rge release <create|list|view> [ver] [--json]\nnotes: .forge/releases/<ver>.md\ndocs: spec/schema.md §Release\ncontract: f0rge --skill\n"1616 }1717 1818 fn spec() -> Vec<args::FlagSpec> {
crates/cli/src/cmd/skill.rs+4 −4
diff --git a/crates/cli/src/cmd/skill.rs b/crates/cli/src/cmd/skill.rsindex abc4ebe..b7d2f24 100644--- a/crates/cli/src/cmd/skill.rs+++ b/crates/cli/src/cmd/skill.rs@@ -3,10 +3,10 @@33 use crate::CliError;44 55 pub fn help() -> &'static str {6-    "f0rge skill: contract\nusage: f0rge [--skill|skill]\nprints schema v1 contract\ncontract: f0rge --skill\n"6+    "f0rge skill: contract\nusage: f0rge [--skill|skill]\nprints schema v1 contract\ndocs: spec/schema.md §Skill\ncontract: f0rge --skill\n"77 }88 9-/// Contract text: <=30 lines, <=900 bytes. Schema v1 decisions only.9+/// Contract text: <=30 lines, <=1000 bytes. Schema v1 decisions only.1010 /// SKILL_TEXT is the single source of truth; skill/skill.md body must equal it.1111 pub const SKILL_TEXT: &str = "f0rge v1: CONTRACT (not help)\n\1212 id: uuid4 lowercase dashes; never changes\n\@@ -16,11 +16,11 @@ tree: issue<->issues/ pr<->prs/ task<->tasks/ ci<->ci/ wiki<->wiki/\n\1616 frontmatter: strict --- fences; unknown field warning; missing required error\n\1717 git: .forge/ committed with repo; PR=git branch from worktree\n\1818 ci: steps are scripts; run in filename order; first nonzero exit code=fail; pass|fail|running|cancelled\n\19-merge: applies prints conflicts never resolves; PR stays (state: merged), branch deleted\n\19+merge: CI-gates patch in scratch worktree; never touches main/branches; prints conflicts, never resolves; you integrate (ff) + delete branch\n\2020 never delete: close/archive only; archive to .forge/archive/\n\2121 wiki: files under wiki/; edit in place/stdin\n\2222 spec: spec/schema.md frozen truth; labels.md/milestones.md registries\n\23-usage: f0rge [--json] <cmd> [args]; init makes .forge/ idempotent; --jq via jq; never parse prose\n\23+usage: f0rge <cmd> [--json] [args]; init makes .forge/ idempotent; --jq via jq; never parse prose\n\2424 json: compact fixed order; empty []/{}\n";2525 2626 pub fn run(args: &[String], _fg: &f0rge_core::Forge) -> Result<(), CliError> {
crates/cli/src/cmd/task.rs+1 −1
diff --git a/crates/cli/src/cmd/task.rs b/crates/cli/src/cmd/task.rsindex 0a4b2fc..04c45e5 100644--- a/crates/cli/src/cmd/task.rs+++ b/crates/cli/src/cmd/task.rs@@ -5,7 +5,7 @@ use f0rge_core::{Forge, Task};55 use serde::Serialize;66 77 pub fn help() -> &'static str {8-    "f0rge task: kanban cards\nusage: f0rge task [--json] <create|list|move|done>\ncreate: --title T [--body B] [--column C]\ncols: todo doing blocked done\ncontract: f0rge --skill\n"8+    "f0rge task: kanban cards\nusage: f0rge task <create|list|move|done> [--json]\ncreate: --title T [--body B] [--column C]\ncols: todo doing blocked done\ndocs: spec/schema.md §Task\ncontract: f0rge --skill\n"99 }1010 1111 const COLS: &[&str] = &["todo", "doing", "blocked", "done"];
crates/cli/src/main.rs+1 −1
diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rsindex a2b2b46..d0878f5 100644--- a/crates/cli/src/main.rs+++ b/crates/cli/src/main.rs@@ -96,7 +96,7 @@ mod budget_tests {9696         let t = crate::cmd::skill::SKILL_TEXT;9797         let (lines, bytes) = lines_and_bytes(t);9898         assert!(lines <= 30, "skill {lines} lines > 30");99-        assert!(bytes <= 900, "skill {bytes} bytes > 900");99+        assert!(bytes <= 1000, "skill {bytes} bytes > 1000");100100     }101101 102102     #[test]
crates/cli/tests/cli.rs+2 −2
diff --git a/crates/cli/tests/cli.rs b/crates/cli/tests/cli.rsindex 623fc17..76fcf5b 100644--- a/crates/cli/tests/cli.rs+++ b/crates/cli/tests/cli.rs@@ -1,6 +1,6 @@11 //! M2 acceptance: budgets enforced on real binary output + init skeleton.22 //! Budgets: root <=12 lines <=300B; sub -h <=8 lines <=250B;3-//! --skill <=30 lines <=900B; errors 1 line + code.3+//! --skill <=30 lines <=1000B; errors 1 line + code.44 55 use std::process::Command;66 @@ -50,7 +50,7 @@ fn skill_within_budget() {5050     assert_eq!(code, 0);5151     let (lines, bytes) = lines_and_bytes(&out);5252     assert!(lines <= 30, "skill {lines} lines:\n{out}");53-    assert!(bytes <= 900, "skill {bytes} bytes:\n{out}");53+    assert!(bytes <= 1000, "skill {bytes} bytes:\n{out}");5454     // content gates: decisions from spec/schema.md must appear.5555     for needle in ["uuid4", "(created,uuid)", "one file per entity", "unknown field warning",5656         "missing required error", "exit code", "close/archive only", "mirrors folder tree"]
skill/skill.md+2 −2
diff --git a/skill/skill.md b/skill/skill.mdindex e9b075d..27079ab 100644--- a/skill/skill.md+++ b/skill/skill.md@@ -10,9 +10,9 @@ tree: issue<->issues/ pr<->prs/ task<->tasks/ ci<->ci/ wiki<->wiki/1010 frontmatter: strict --- fences; unknown field warning; missing required error1111 git: .forge/ committed with repo; PR=git branch from worktree1212 ci: steps are scripts; run in filename order; first nonzero exit code=fail; pass|fail|running|cancelled13-merge: applies prints conflicts never resolves; PR stays (state: merged), branch deleted13+merge: CI-gates patch in scratch worktree; never touches main/branches; prints conflicts, never resolves; you integrate (ff) + delete branch1414 never delete: close/archive only; archive to .forge/archive/1515 wiki: files under wiki/; edit in place/stdin1616 spec: spec/schema.md frozen truth; labels.md/milestones.md registries17-usage: f0rge [--json] <cmd> [args]; init makes .forge/ idempotent; --jq via jq; never parse prose17+usage: f0rge <cmd> [--json] [args]; init makes .forge/ idempotent; --jq via jq; never parse prose1818 json: compact fixed order; empty []/{}
spec/schema.md+3 −2
diff --git a/spec/schema.md b/spec/schema.mdindex 1099da6..6e70364 100644--- a/spec/schema.md+++ b/spec/schema.md@@ -6,7 +6,7 @@ without a schema-version bump in the contract (`f0rge --skill`).66 ## 0. Global rules77 88 - Identity: `id` is uuid4, lowercase, dashes kept. Never changes.9-- Timestamps: RFC3339 UTC, second precision: `2025-09-08T02:00:00Z`.9+- Timestamps: RFC3339 UTC with fractional seconds: `2025-09-08T02:00:00.123456Z` (parser accepts plain `...00Z` too).1010   Lexicographic order == chronological order. Field names: `created`,1111   `updated`, `closed`, `due` (milestones: `YYYY-MM-DD` or full RFC3339).1212 - Files: YAML frontmatter between `---` fences + markdown body.@@ -85,6 +85,7 @@ Body = notes. Golden: `spec/fixtures/release.md`.8585 Required: `pr` (uuid), `verdict: pass|fail`, `created`.8686 Optional: `applied: bool` (false), `resolved: [str]` ([]).8787 Body = notes. Golden: `spec/fixtures/merge-record.md`.88+Gate only: CI-gates patch in scratch worktree; never touches main/branches; prints conflicts, never resolves; you integrate (ff) + delete branch.8889 8990 ### ci-env — `.forge/ci/ci.env` (YAML mapping, no fences)9091 Known: `runner: local|docker` (default local).@@ -120,7 +121,7 @@ Golden: `spec/fixtures/ci-run.md`.120121 ### wiki — files are the API; index derived, never stored121122 - `wiki get <path>`, `wiki put <path> -` (stdin only), `wiki list`.122123 - Index: walk tree (symlinks skipped, files > 256 KiB skipped),123-  title = first `# H1` else filename stem; sorted by path.124+  index title = first H1, else filename; sorted by path.124125 - Paths are wiki-relative: absolute, `.`, `..` rejected125126   (`escapes wiki`, exit 1); missing page (`not found`, exit 1);126127   directories are not pages (`is a directory`, exit 1).