1st commit
This commit is contained in:
parent
db728ac2e4
commit
d78b0d83c8
|
|
@ -146,7 +146,7 @@ Initial discussion for feature `{fid}`. Append your comments below.
|
||||||
## Participation
|
## Participation
|
||||||
- Maintainer: Kickoff. VOTE: READY
|
- Maintainer: Kickoff. VOTE: READY
|
||||||
"""
|
"""
|
||||||
write_text(dir_disc / "feature.discussion.md", req)
|
write_text(dir_disc / "feature.feature.discussion.md", req)
|
||||||
|
|
||||||
sum_md = f"""# Summary — Feature
|
sum_md = f"""# Summary — Feature
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -699,6 +699,7 @@ def parse_args():
|
||||||
p.add_argument("--prompt", default="Explain your new feature idea")
|
p.add_argument("--prompt", default="Explain your new feature idea")
|
||||||
p.add_argument("--fields", nargs="+", default=["Summary","Title","Intent","ProblemItSolves","BriefOverview"])
|
p.add_argument("--fields", nargs="+", default=["Summary","Title","Intent","ProblemItSolves","BriefOverview"])
|
||||||
p.add_argument("--criteria", default="", help="JSON mapping of field -> criteria")
|
p.add_argument("--criteria", default="", help="JSON mapping of field -> criteria")
|
||||||
|
p.add_argument("--hints", default="", help="JSON list of hint strings")
|
||||||
p.add_argument("--timeout", type=int, default=90)
|
p.add_argument("--timeout", type=int, default=90)
|
||||||
p.add_argument("--tail", type=int, default=6000)
|
p.add_argument("--tail", type=int, default=6000)
|
||||||
p.add_argument("--debug", action="store_true")
|
p.add_argument("--debug", action="store_true")
|
||||||
|
|
@ -733,11 +734,23 @@ if __name__ == "__main__":
|
||||||
print("[FATAL] 'requests' is required for image backends. pip install requests", file=sys.stderr)
|
print("[FATAL] 'requests' is required for image backends. pip install requests", file=sys.stderr)
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
|
|
||||||
|
# Parse JSON args (tolerate empty/invalid)
|
||||||
|
try:
|
||||||
|
criteria = json.loads(args.criteria) if args.criteria else {}
|
||||||
|
if not isinstance(criteria, dict): criteria = {}
|
||||||
|
except Exception:
|
||||||
|
criteria = {}
|
||||||
|
try:
|
||||||
|
hints = json.loads(args.hints) if args.hints else None
|
||||||
|
if hints is not None and not isinstance(hints, list): hints = None
|
||||||
|
except Exception:
|
||||||
|
hints = None
|
||||||
|
|
||||||
demo = open_ramble_dialog(
|
demo = open_ramble_dialog(
|
||||||
prompt=args.prompt,
|
prompt=args.prompt,
|
||||||
fields=args.fields,
|
fields=args.fields,
|
||||||
field_criteria=criteria,
|
field_criteria=criteria,
|
||||||
hints=None,
|
hints = hints,
|
||||||
provider=provider,
|
provider=provider,
|
||||||
enable_stability=args.stability,
|
enable_stability=args.stability,
|
||||||
enable_pexels=args.pexels,
|
enable_pexels=args.pexels,
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
type: discussion
|
|
||||||
stage: <feature|design|implementation|testing|review>
|
|
||||||
status: OPEN
|
|
||||||
feature_id: <FR_...>
|
|
||||||
created: <YYYY-MM-DD>
|
|
||||||
|
|
||||||
promotion_rule:
|
|
||||||
allow_agent_votes: true
|
|
||||||
ready_min_eligible_votes: all
|
|
||||||
reject_min_eligible_votes: all
|
|
||||||
|
|
||||||
participation:
|
|
||||||
instructions: |
|
|
||||||
- Append your input at the end as: "YourName: your comment…"
|
|
||||||
- Every comment must end with a vote line: "VOTE: READY|CHANGES|REJECT"
|
|
||||||
- Agents/bots must prefix names with "AI_"
|
|
||||||
|
|
||||||
voting:
|
|
||||||
values: [READY, CHANGES, REJECT]
|
|
||||||
---
|
|
||||||
## Summary
|
|
||||||
2-4 sentence summary of current state
|
|
||||||
|
|
||||||
## Participation
|
|
||||||
comments appended below
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!--META
|
||||||
|
{
|
||||||
|
"kind": "discussion",
|
||||||
|
"tokens": ["FeatureId", "CreatedDate"]
|
||||||
|
}
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
Initial discussion for {FeatureId}. Append your comments below.
|
||||||
|
|
||||||
|
## Participation
|
||||||
|
- Maintainer: Kickoff. VOTE: READY
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<!--META
|
||||||
|
{
|
||||||
|
"kind": "discussion_summary",
|
||||||
|
"tokens": ["FeatureId", "CreatedDate"]
|
||||||
|
}
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Summary — Feature {FeatureId}
|
||||||
|
|
||||||
|
<!-- SUMMARY:DECISIONS START -->
|
||||||
|
## Decisions (ADR-style)
|
||||||
|
- (none yet)
|
||||||
|
<!-- SUMMARY:DECISIONS END -->
|
||||||
|
|
||||||
|
<!-- SUMMARY:OPEN_QUESTIONS START -->
|
||||||
|
## Open Questions
|
||||||
|
- (none yet)
|
||||||
|
<!-- SUMMARY:OPEN_QUESTIONS END -->
|
||||||
|
|
||||||
|
<!-- SUMMARY:AWAITING START -->
|
||||||
|
## Awaiting Replies
|
||||||
|
- (none yet)
|
||||||
|
<!-- SUMMARY:AWAITING END -->
|
||||||
|
|
||||||
|
<!-- SUMMARY:ACTION_ITEMS START -->
|
||||||
|
## Action Items
|
||||||
|
- (none yet)
|
||||||
|
<!-- SUMMARY:ACTION_ITEMS END -->
|
||||||
|
|
||||||
|
<!-- SUMMARY:VOTES START -->
|
||||||
|
## Votes (latest per participant)
|
||||||
|
READY: 1 • CHANGES: 0 • REJECT: 0
|
||||||
|
- Maintainer
|
||||||
|
<!-- SUMMARY:VOTES END -->
|
||||||
|
|
||||||
|
<!-- SUMMARY:TIMELINE START -->
|
||||||
|
## Timeline (most recent first)
|
||||||
|
- {CreatedDate} Maintainer: Kickoff
|
||||||
|
<!-- SUMMARY:TIMELINE END -->
|
||||||
|
|
||||||
|
<!-- SUMMARY:LINKS START -->
|
||||||
|
## Links
|
||||||
|
- Design/Plan: ../design/design.md
|
||||||
|
<!-- SUMMARY:LINKS END -->
|
||||||
|
|
@ -1,10 +1,35 @@
|
||||||
# Feature Request: <title>
|
# Feature Request: <title>
|
||||||
|
|
||||||
**Feature ID**: <FR_YYYY-MM-DD_slug>
|
<!--META
|
||||||
**Intent**: <one paragraph describing purpose>
|
{
|
||||||
**Motivation / Problem**: <why this is needed now>
|
"kind": "feature_request",
|
||||||
**Constraints / Non-Goals**: <bulleted list of limitations>
|
"ramble_fields": [
|
||||||
**Rough Proposal**: <short implementation outline>
|
{"name": "Title", "hint": "camelCase, ≤24 chars", "default": "initialProjectDesign"},
|
||||||
**Open Questions**: <bulleted list of uncertainties>
|
{"name": "Intent"},
|
||||||
**Meta**: Created: <date> • Author: <name>
|
{"name": "ProblemItSolves"},
|
||||||
Discussion Template (process/templates/discussion.md):
|
{"name": "BriefOverview"},
|
||||||
|
{"name": "Summary", "hint": "≤2 sentences"}
|
||||||
|
],
|
||||||
|
"criteria": {
|
||||||
|
"Title": "camelCase, <= 24 chars",
|
||||||
|
"Summary": "<= 2 sentences"
|
||||||
|
},
|
||||||
|
"hints": [
|
||||||
|
"What is it called?",
|
||||||
|
"Who benefits most?",
|
||||||
|
"What problem does it solve?",
|
||||||
|
"What does success look like?"
|
||||||
|
],
|
||||||
|
"tokens": ["FeatureId", "CreatedDate", "Title", "Intent", "ProblemItSolves", "BriefOverview", "Summary"]
|
||||||
|
}
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Feature Request: {Title}
|
||||||
|
|
||||||
|
**Intent**: {Intent}
|
||||||
|
**Motivation / Problem**: {ProblemItSolves}
|
||||||
|
**Brief Overview**: {BriefOverview}
|
||||||
|
|
||||||
|
**Summary**: {Summary}
|
||||||
|
|
||||||
|
**Meta**: FeatureId: {FeatureId} • Created: {CreatedDate}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
*.egg-info/
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
|
# Node (if any JS tooling appears)
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Env / secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
secrets/
|
||||||
|
|
||||||
|
# OS/editor
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Project
|
||||||
|
.git/ai-rules-*
|
||||||
|
|
@ -850,7 +850,7 @@ rules:
|
||||||
feature_request:
|
feature_request:
|
||||||
outputs:
|
outputs:
|
||||||
feature_discussion:
|
feature_discussion:
|
||||||
path: "{dir}/discussions/feature.discussion.md"
|
path: "{dir}/discussions/feature.feature.discussion.md"
|
||||||
output_type: "feature_discussion_writer"
|
output_type: "feature_discussion_writer"
|
||||||
instruction: |
|
instruction: |
|
||||||
If missing: create with standard header (stage: feature, status: OPEN),
|
If missing: create with standard header (stage: feature, status: OPEN),
|
||||||
|
|
@ -870,7 +870,7 @@ rules:
|
||||||
outputs:
|
outputs:
|
||||||
# 1) Append the new AI comment to the discussion (append-only)
|
# 1) Append the new AI comment to the discussion (append-only)
|
||||||
self_append:
|
self_append:
|
||||||
path: "{dir}/discussions/feature.discussion.md"
|
path: "{dir}/discussions/feature.feature.discussion.md"
|
||||||
output_type: "feature_discussion_writer"
|
output_type: "feature_discussion_writer"
|
||||||
instruction: |
|
instruction: |
|
||||||
Append concise comment signed with AI name, ending with a single vote line.
|
Append concise comment signed with AI name, ending with a single vote line.
|
||||||
|
|
@ -890,7 +890,7 @@ rules:
|
||||||
|
|
||||||
# 3) Promotion artifacts when READY_FOR_DESIGN
|
# 3) Promotion artifacts when READY_FOR_DESIGN
|
||||||
design_discussion:
|
design_discussion:
|
||||||
path: "{dir}/discussions/design.discussion.md"
|
path: "{dir}/discussions/design.feature.discussion.md"
|
||||||
output_type: "design_discussion_writer"
|
output_type: "design_discussion_writer"
|
||||||
instruction: |
|
instruction: |
|
||||||
Create ONLY if feature discussion status is READY_FOR_DESIGN.
|
Create ONLY if feature discussion status is READY_FOR_DESIGN.
|
||||||
|
|
@ -930,7 +930,7 @@ rules:
|
||||||
Update only the marker-bounded sections from the discussion content.
|
Update only the marker-bounded sections from the discussion content.
|
||||||
|
|
||||||
impl_discussion:
|
impl_discussion:
|
||||||
path: "{dir}/discussions/implementation.discussion.md"
|
path: "{dir}/discussions/implementation.feature.discussion.md"
|
||||||
output_type: "impl_discussion_writer"
|
output_type: "impl_discussion_writer"
|
||||||
instruction: |
|
instruction: |
|
||||||
Create ONLY if design discussion status is READY_FOR_IMPLEMENTATION.
|
Create ONLY if design discussion status is READY_FOR_IMPLEMENTATION.
|
||||||
|
|
@ -974,7 +974,7 @@ rules:
|
||||||
Include unchecked items from ../implementation/tasks.md in ACTION_ITEMS.
|
Include unchecked items from ../implementation/tasks.md in ACTION_ITEMS.
|
||||||
|
|
||||||
test_discussion:
|
test_discussion:
|
||||||
path: "{dir}/discussions/testing.discussion.md"
|
path: "{dir}/discussions/testing.feature.discussion.md"
|
||||||
output_type: "test_discussion_writer"
|
output_type: "test_discussion_writer"
|
||||||
instruction: |
|
instruction: |
|
||||||
Create ONLY if implementation status is READY_FOR_TESTING.
|
Create ONLY if implementation status is READY_FOR_TESTING.
|
||||||
|
|
@ -1024,7 +1024,7 @@ rules:
|
||||||
Initialize bug discussion and fix plan in the same folder.
|
Initialize bug discussion and fix plan in the same folder.
|
||||||
|
|
||||||
review_discussion:
|
review_discussion:
|
||||||
path: "{dir}/discussions/review.discussion.md"
|
path: "{dir}/discussions/review.feature.discussion.md"
|
||||||
output_type: "review_discussion_writer"
|
output_type: "review_discussion_writer"
|
||||||
instruction: |
|
instruction: |
|
||||||
Create ONLY if all test checklist items pass.
|
Create ONLY if all test checklist items pass.
|
||||||
|
|
@ -1141,7 +1141,7 @@ resolve_template() {
|
||||||
ext="${basename##*.}"
|
ext="${basename##*.}"
|
||||||
# nearest FR_* ancestor as feature_id
|
# nearest FR_* ancestor as feature_id
|
||||||
feature_id="$(echo "$rel_path" | sed -n 's|.*Docs/features/\(FR_[^/]*\).*|\1|p')"
|
feature_id="$(echo "$rel_path" | sed -n 's|.*Docs/features/\(FR_[^/]*\).*|\1|p')"
|
||||||
# infer stage from <stage>.discussion.md when applicable
|
# infer stage from <stage>.feature.discussion.md when applicable
|
||||||
stage="$(echo "$basename" | sed -n 's/^\([A-Za-z0-9_-]\+\)\.discussion\.md$/\1/p')"
|
stage="$(echo "$basename" | sed -n 's/^\([A-Za-z0-9_-]\+\)\.discussion\.md$/\1/p')"
|
||||||
echo "$tmpl" \
|
echo "$tmpl" \
|
||||||
| sed -e "s|{date}|$today|g" \
|
| sed -e "s|{date}|$today|g" \
|
||||||
|
|
@ -1288,7 +1288,7 @@ Rule Definition (in Docs/features/.ai-rules.yml):
|
||||||
discussion_moderator_nudge:
|
discussion_moderator_nudge:
|
||||||
outputs:
|
outputs:
|
||||||
self_append:
|
self_append:
|
||||||
path: "{dir}/discussions/{stage}.discussion.md"
|
path: "{dir}/discussions/{stage}.feature.discussion.md"
|
||||||
output_type: "discussion_moderator_writer"
|
output_type: "discussion_moderator_writer"
|
||||||
instruction: |
|
instruction: |
|
||||||
Act as AI_Moderator. Analyze the entire discussion and:
|
Act as AI_Moderator. Analyze the entire discussion and:
|
||||||
|
|
@ -1425,7 +1425,7 @@ Bypass & Minimal Patch:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.git/ai-rules-debug/
|
.git/ai-rules-debug/
|
||||||
├─ 20251021-143022-12345-feature.discussion.md/
|
├─ 20251021-143022-12345-feature.feature.discussion.md/
|
||||||
│ ├─ raw.out # Raw model output
|
│ ├─ raw.out # Raw model output
|
||||||
│ ├─ clean.diff # Extracted patch
|
│ ├─ clean.diff # Extracted patch
|
||||||
│ ├─ sanitized.diff # After sanitization
|
│ ├─ sanitized.diff # After sanitization
|
||||||
|
|
@ -1975,7 +1975,7 @@ Docs/features/FR_.../
|
||||||
type: discussion-summary
|
type: discussion-summary
|
||||||
stage: feature # feature|design|implementation|testing|review
|
stage: feature # feature|design|implementation|testing|review
|
||||||
status: ACTIVE # ACTIVE|SNAPSHOT|ARCHIVED
|
status: ACTIVE # ACTIVE|SNAPSHOT|ARCHIVED
|
||||||
source_discussion: feature.discussion.md
|
source_discussion: feature.feature.discussion.md
|
||||||
feature_id: FR_YYYY-MM-DD_<slug>
|
feature_id: FR_YYYY-MM-DD_<slug>
|
||||||
updated: YYYY-MM-DDTHH:MM:SSZ
|
updated: YYYY-MM-DDTHH:MM:SSZ
|
||||||
policy:
|
policy:
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ Examples:
|
||||||
python setup_cascadingdev.py --target ~/dev/my-new-repo
|
python setup_cascadingdev.py --target ~/dev/my-new-repo
|
||||||
python setup_cascadingdev.py --target /abs/path --no-ramble
|
python setup_cascadingdev.py --target /abs/path --no-ramble
|
||||||
"""
|
"""
|
||||||
import json
|
import json, re
|
||||||
import argparse
|
import argparse
|
||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -57,15 +57,78 @@ def run(cmd: list[str], cwd: Path | None = None) -> int:
|
||||||
proc = subprocess.Popen(cmd, cwd=cwd, stdout=sys.stdout, stderr=sys.stderr)
|
proc = subprocess.Popen(cmd, cwd=cwd, stdout=sys.stdout, stderr=sys.stderr)
|
||||||
return proc.wait()
|
return proc.wait()
|
||||||
|
|
||||||
|
# --- Tiny template helpers ----------------------------------------------------
|
||||||
|
# Self-contained; no external dependencies
|
||||||
|
_META_RE = re.compile(r"<!--META\s*(\{.*?\})\s*-->", re.S)
|
||||||
|
|
||||||
|
def load_template_with_meta(path: Path) -> tuple[dict, str]:
|
||||||
|
"""
|
||||||
|
Returns (meta: dict, body_without_meta: str). If no META, ({} , full text).
|
||||||
|
META must be a single JSON object inside <!--META ... -->.
|
||||||
|
"""
|
||||||
|
if not path.exists():
|
||||||
|
return {}, ""
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
m = _META_RE.search(text)
|
||||||
|
if not m:
|
||||||
|
return {}, text
|
||||||
|
meta_json = m.group(1)
|
||||||
|
try:
|
||||||
|
meta = json.loads(meta_json)
|
||||||
|
except Exception:
|
||||||
|
meta = {}
|
||||||
|
body = _META_RE.sub("", text, count=1).lstrip()
|
||||||
|
return meta, body
|
||||||
|
|
||||||
|
def render_placeholders(body: str, values: dict) -> str:
|
||||||
|
"""
|
||||||
|
Simple {Token} replacement. Leaves unknown tokens as-is.
|
||||||
|
"""
|
||||||
|
# two-pass: {{Token}} then {Token}
|
||||||
|
out = body
|
||||||
|
for k, v in values.items():
|
||||||
|
out = out.replace("{{" + k + "}}", str(v))
|
||||||
|
try:
|
||||||
|
out = out.format_map({k: v for k, v in values.items()})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return out
|
||||||
|
|
||||||
|
def meta_ramble_config(meta: dict) -> tuple[list[str], dict, dict, list[str]]:
|
||||||
|
"""
|
||||||
|
From template META, extract:
|
||||||
|
- fields: list of field names in order
|
||||||
|
- defaults: {field: default_value}
|
||||||
|
- criteria: {field: rule/description} (optional)
|
||||||
|
- hints: [str, ...] (optional)
|
||||||
|
"""
|
||||||
|
fields: list[str] = []
|
||||||
|
defaults: dict = {}
|
||||||
|
for spec in meta.get("ramble_fields", []):
|
||||||
|
name = spec.get("name")
|
||||||
|
if name:
|
||||||
|
fields.append(name)
|
||||||
|
if "default" in spec:
|
||||||
|
defaults[name] = spec["default"]
|
||||||
|
criteria = meta.get("criteria", {}) or {}
|
||||||
|
hints = meta.get("hints", []) or []
|
||||||
|
return fields, defaults, criteria, hints
|
||||||
|
|
||||||
def ensure_git_repo(target: Path):
|
def ensure_git_repo(target: Path):
|
||||||
"""Initialize a git repository if one doesn't exist at the target path."""
|
"""Initialize a git repository if one doesn't exist at the target path."""
|
||||||
if not (target / ".git").exists():
|
if not (target / ".git").exists():
|
||||||
# Initialize git repo with main branch
|
# Initialize git repo with main branch
|
||||||
run(["git", "init", "-b", "main"], cwd=target)
|
run(["git", "init", "-b", "main"], cwd=target)
|
||||||
# Create basic .gitignore file
|
# Seed .gitignore from template if present; otherwise fallback
|
||||||
|
tmpl_gitignore = INSTALL_ROOT / "assets" / "templates" / "root_gitignore"
|
||||||
|
if tmpl_gitignore.exists():
|
||||||
|
copy_if_missing(tmpl_gitignore, target / ".gitignore")
|
||||||
|
else:
|
||||||
write_if_missing(target / ".gitignore", "\n".join([
|
write_if_missing(target / ".gitignore", "\n".join([
|
||||||
".env", ".env.*", "secrets/", ".git/ai-rules-*", "__pycache__/",
|
"__pycache__/", "*.py[cod]", "*.egg-info/", ".pytest_cache/",
|
||||||
"*.pyc", ".pytest_cache/", ".DS_Store",
|
".mypy_cache/", ".coverage", "htmlcov/", "node_modules/",
|
||||||
|
"dist/", "build/", ".env", ".env.*", "secrets/", ".DS_Store",
|
||||||
|
".git/ai-rules-*",
|
||||||
]) + "\n")
|
]) + "\n")
|
||||||
|
|
||||||
def install_precommit_hook(target: Path):
|
def install_precommit_hook(target: Path):
|
||||||
|
|
@ -90,21 +153,34 @@ def run_ramble_and_collect(target: Path, provider: str = "mock", claude_cmd: str
|
||||||
Launch Ramble GUI to collect initial feature request details.
|
Launch Ramble GUI to collect initial feature request details.
|
||||||
Falls back to terminal prompts if GUI fails or returns invalid JSON.
|
Falls back to terminal prompts if GUI fails or returns invalid JSON.
|
||||||
"""
|
"""
|
||||||
|
# Find FR template + read META (for field names)
|
||||||
|
fr_tmpl = INSTALL_ROOT / "assets" / "templates" / "feature_request.md"
|
||||||
|
meta, _ = load_template_with_meta(fr_tmpl)
|
||||||
|
field_names, _defaults, criteria, hints = meta_ramble_config(meta)
|
||||||
|
|
||||||
|
# Fallback to your previous default fields if template lacks META
|
||||||
|
if not field_names:
|
||||||
|
field_names = ["Summary", "Title", "Intent", "ProblemItSolves", "BriefOverview"]
|
||||||
|
|
||||||
ramble = target / "ramble.py"
|
ramble = target / "ramble.py"
|
||||||
if not ramble.exists():
|
if not ramble.exists():
|
||||||
say("[-] ramble.py not found in target; skipping interactive FR capture.")
|
say("[-] ramble.py not found in target; skipping interactive FR capture.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Build Ramble command arguments
|
# Build Ramble arguments dynamically from the template-defined fields
|
||||||
args = [
|
args = [
|
||||||
sys.executable, str(ramble),
|
sys.executable, str(ramble),
|
||||||
"--provider", provider,
|
"--provider", provider,
|
||||||
"--claude-cmd", claude_cmd,
|
"--claude-cmd", claude_cmd,
|
||||||
"--prompt", "Describe your initial feature request for this repository",
|
"--prompt", "Describe your initial feature request for this repository",
|
||||||
"--fields", "Summary", "Title", "Intent", "ProblemItSolves", "BriefOverview",
|
"--fields", *field_names,
|
||||||
"--criteria", '{"Summary":"<= 2 sentences","Title":"camelCase, <= 24 chars"}'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if criteria:
|
||||||
|
args += ["--criteria", json.dumps(criteria)]
|
||||||
|
if hints:
|
||||||
|
args += ["--hints", json.dumps(hints)]
|
||||||
|
|
||||||
say("[•] Launching Ramble (close the dialog with Submit to return JSON)…")
|
say("[•] Launching Ramble (close the dialog with Submit to return JSON)…")
|
||||||
proc = subprocess.run(args, text=True, capture_output=True, cwd=str(target))
|
proc = subprocess.run(args, text=True, capture_output=True, cwd=str(target))
|
||||||
|
|
||||||
|
|
@ -170,51 +246,70 @@ def seed_process_and_rules(target: Path):
|
||||||
copy_if_missing(t_rules_features, rules_dir / ".ai-rules.yml")
|
copy_if_missing(t_rules_features, rules_dir / ".ai-rules.yml")
|
||||||
|
|
||||||
def seed_initial_feature(target: Path, req_fields: dict | None):
|
def seed_initial_feature(target: Path, req_fields: dict | None):
|
||||||
"""Create the initial feature request and associated discussion files."""
|
|
||||||
today = datetime.date.today().isoformat()
|
today = datetime.date.today().isoformat()
|
||||||
fr_dir = target / "Docs" / "features" / f"FR_{today}_initial-feature-request"
|
feature_id = f"FR_{today}_initial-feature-request"
|
||||||
|
fr_dir = target / "Docs" / "features" / feature_id
|
||||||
disc_dir = fr_dir / "discussions"
|
disc_dir = fr_dir / "discussions"
|
||||||
disc_dir.mkdir(parents=True, exist_ok=True)
|
disc_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Create feature request content, using Ramble data if available
|
# Gather values from Ramble result (if any)
|
||||||
if req_fields:
|
fields = (req_fields or {}).get("fields", {}) if req_fields else {}
|
||||||
title = (req_fields.get("fields", {}) or {}).get("Title", "").strip() or "initialProjectDesign"
|
# Load FR template + META
|
||||||
intent = (req_fields.get("fields", {}) or {}).get("Intent", "").strip() or "—"
|
fr_tmpl = INSTALL_ROOT / "assets" / "templates" / "feature_request.md"
|
||||||
problem = (req_fields.get("fields", {}) or {}).get("ProblemItSolves", "").strip() or "—"
|
fr_meta, fr_body = load_template_with_meta(fr_tmpl)
|
||||||
brief = (req_fields.get("fields", {}) or {}).get("BriefOverview", "").strip() or "—"
|
field_names, defaults, _criteria, _hints = meta_ramble_config(fr_meta)
|
||||||
summary = (req_fields.get("summary") or "").strip()
|
|
||||||
body = f"""# Feature Request: {title}
|
# Build values map with defaults → ramble fields → system tokens
|
||||||
|
values = {}
|
||||||
|
values.update(defaults) # template defaults
|
||||||
|
values.update(fields) # user-entered
|
||||||
|
values.update({ # system tokens
|
||||||
|
"FeatureId": feature_id,
|
||||||
|
"CreatedDate": today,
|
||||||
|
})
|
||||||
|
|
||||||
|
# If no template body, fall back to your old default
|
||||||
|
if not fr_body.strip():
|
||||||
|
title = values.get("Title", "initialProjectDesign")
|
||||||
|
intent = values.get("Intent", "—")
|
||||||
|
problem = values.get("ProblemItSolves", "—")
|
||||||
|
brief = values.get("BriefOverview", "—")
|
||||||
|
summary = values.get("Summary", "")
|
||||||
|
fr_body = f"""# Feature Request: {title}
|
||||||
|
|
||||||
**Intent**: {intent}
|
**Intent**: {intent}
|
||||||
**Motivation / Problem**: {problem}
|
**Motivation / Problem**: {problem}
|
||||||
**Brief Overview**: {brief}
|
**Brief Overview**: {brief}
|
||||||
|
|
||||||
**Summary**: {summary}
|
**Summary**: {summary}
|
||||||
|
|
||||||
**Meta**: Created: {today}
|
**Meta**: Created: {today}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
(fr_dir / "request.md").write_text(render_placeholders(fr_body, values), encoding="utf-8")
|
||||||
|
|
||||||
|
# --- feature.discussion.md ---
|
||||||
|
disc_tmpl = INSTALL_ROOT / "assets" / "templates" / "feature.discussion.md"
|
||||||
|
d_meta, d_body = load_template_with_meta(disc_tmpl)
|
||||||
|
# Always include the front-matter for rules, then template body (or fallback)
|
||||||
|
fm = f"""---\ntype: discussion\nstage: feature\nstatus: OPEN\nfeature_id: {feature_id}\ncreated: {today}\n---\n"""
|
||||||
|
if not d_body.strip():
|
||||||
|
d_body = (
|
||||||
|
"## Summary\n"
|
||||||
|
f"Initial discussion for {feature_id}. Append your comments below.\n\n"
|
||||||
|
"## Participation\n"
|
||||||
|
"- Maintainer: Kickoff. VOTE: READY\n"
|
||||||
|
)
|
||||||
|
(disc_dir / "feature.discussion.md").write_text(fm + render_placeholders(d_body, values), encoding="utf-8")
|
||||||
|
|
||||||
|
# --- feature.discussion.sum.md ---
|
||||||
|
sum_tmpl = INSTALL_ROOT / "assets" / "templates" / "feature.discussion.sum.md"
|
||||||
|
s_meta, s_body = load_template_with_meta(sum_tmpl)
|
||||||
|
if s_body.strip():
|
||||||
|
# use template
|
||||||
|
(disc_dir / "feature.discussion.sum.md").write_text(render_placeholders(s_body, values), encoding="utf-8")
|
||||||
else:
|
else:
|
||||||
# Fallback to template content if no Ramble data
|
# your existing static content
|
||||||
body = (target / "process" / "templates" / "feature_request.md").read_text(encoding="utf-8")
|
|
||||||
|
|
||||||
(fr_dir / "request.md").write_text(body, encoding="utf-8")
|
|
||||||
|
|
||||||
# Create initial discussion file
|
|
||||||
(disc_dir / "feature.discussion.md").write_text(
|
|
||||||
f"""---
|
|
||||||
type: discussion
|
|
||||||
stage: feature
|
|
||||||
status: OPEN
|
|
||||||
feature_id: FR_{today}_initial-feature-request
|
|
||||||
created: {today}
|
|
||||||
---
|
|
||||||
## Summary
|
|
||||||
Initial discussion for the first feature request. Append your comments below.
|
|
||||||
|
|
||||||
## Participation
|
|
||||||
- Maintainer: Kickoff. VOTE: READY
|
|
||||||
""", encoding="utf-8")
|
|
||||||
|
|
||||||
# Create companion summary file with structured sections
|
|
||||||
(disc_dir / "feature.discussion.sum.md").write_text(
|
(disc_dir / "feature.discussion.sum.md").write_text(
|
||||||
"""# Summary — Feature
|
"""# Summary — Feature
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,15 @@ def main():
|
||||||
shutil.copy2(ROOT / "assets" / "hooks" / "pre-commit", BUNDLE / "assets" / "hooks" / "pre-commit")
|
shutil.copy2(ROOT / "assets" / "hooks" / "pre-commit", BUNDLE / "assets" / "hooks" / "pre-commit")
|
||||||
|
|
||||||
# copy core templates
|
# copy core templates
|
||||||
for t in ["feature_request.md","discussion.md","design_doc.md","USER_GUIDE.md"]:
|
|
||||||
|
for t in [
|
||||||
|
"feature_request.md",
|
||||||
|
"feature.discussion.md",
|
||||||
|
"feature.discussion.sum.md",
|
||||||
|
"design_doc.md",
|
||||||
|
"USER_GUIDE.md",
|
||||||
|
"root_gitignore",
|
||||||
|
]:
|
||||||
shutil.copy2(ROOT / "assets" / "templates" / t, BUNDLE / "assets" / "templates" / t)
|
shutil.copy2(ROOT / "assets" / "templates" / t, BUNDLE / "assets" / "templates" / t)
|
||||||
|
|
||||||
# copy (recursively) the contents of process/ and rules/ templates folders
|
# copy (recursively) the contents of process/ and rules/ templates folders
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ def main():
|
||||||
required = [
|
required = [
|
||||||
root / "assets" / "hooks" / "pre-commit",
|
root / "assets" / "hooks" / "pre-commit",
|
||||||
root / "assets" / "templates" / "feature_request.md",
|
root / "assets" / "templates" / "feature_request.md",
|
||||||
root / "assets" / "templates" / "discussion.md",
|
root / "assets" / "templates" / "feature.discussion.md",
|
||||||
root / "assets" / "templates" / "design_doc.md",
|
root / "assets" / "templates" / "design_doc.md",
|
||||||
root / "assets" / "templates" / "USER_GUIDE.md", # now required
|
root / "assets" / "templates" / "USER_GUIDE.md", # now required
|
||||||
root / "assets" / "runtime" / "ramble.py",
|
root / "assets" / "runtime" / "ramble.py",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue