Skip to content
This repository was archived by the owner on Apr 11, 2026. It is now read-only.

Commit 887c5ce

Browse files
z23ccclaude
andcommitted
feat: maximize project-context.md across all pipeline stages
Core: - Add parse_project_context module (ProjectContext, GuardCommands structs) - Parse yaml blocks for Guard Commands and File Conventions - infer_domain() matches file paths to domains via conventions - conflicts_with_non_goals() keyword matching - flowctl project-context show/domain commands Guard integration: - Guard reads Guard Commands from project-context as highest priority - Falls back to stack config → auto-detection Init generation: - Auto-detect guard commands (test/lint/typecheck/format per stack) - Auto-detect file conventions (scan actual directories) - Auto-detect critical rules (unsafe_code, strict TS, CI) - Refactored into detect_stack/generate_guard_commands/generate_file_conventions/detect_rules Skills/prompts updated (7 files): - brainstorm: reads Non-Goals + Architecture Decisions - plan: reads Non-Goals, File Conventions, Architecture Decisions - plan_review: verifies against Non-Goals - close: Non-Goals compliance in pre-launch checklist - Edge Case Hunter: checks Critical Rules violations - Acceptance Auditor: checks Non-Goals compliance - Worker spawn: includes Non-Goals + Critical Rules in prompt 8 new tests, 359 total pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 82faa92 commit 887c5ce

16 files changed

Lines changed: 996 additions & 31 deletions

File tree

bin/flowctl

16.2 KB
Binary file not shown.

codex/skills/flow-code-plan/steps/step-02-research.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ $FLOWCTL config get scouts.github --json
1414
$FLOWCTL stack show --json
1515
```
1616

17+
## Read Project Context
18+
19+
Read `.flow/project-context.md` if it exists. Use Non-Goals to scope out excluded approaches. Use File Conventions to auto-assign task domains. Use Architecture Decisions to avoid proposing alternatives to settled choices.
20+
1721
## Check Architecture Invariants
1822

1923
```bash

codex/skills/flow-code-work/steps/step-04-spawn-workers.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ Determine the RP_CONTEXT tier (check in order, first match wins):
7777
- **Tier 2 (CLI)**: `which rp-cli >/dev/null 2>&1` succeeds -> `RP_CONTEXT=cli`
7878
- **Tier 3 (fallback)**: Neither available -> `RP_CONTEXT=none`
7979

80+
## Read Project Context for Workers
81+
82+
If `.flow/project-context.md` exists, read the Non-Goals and Critical Implementation Rules sections. Include these in the worker prompt so workers don't violate constraints.
83+
8084
## Worker Prompt Generation
8185

8286
Use `flowctl worker-prompt --bootstrap` to generate a minimal bootstrap prompt for each worker. This outputs a ~200 token prompt that instructs the worker to call `worker-phase next` in a loop, fetching full phase instructions on demand.
@@ -110,6 +114,8 @@ Agent({
110114
RP_CONTEXT: $RP_CONTEXT
111115
TEAM_MODE: true
112116
OWNED_FILES: <comma-separated file list from Step 6>
117+
PROJECT_CONTEXT_NON_GOALS: <Non-Goals from .flow/project-context.md if exists>
118+
PROJECT_CONTEXT_CRITICAL_RULES: <Critical Implementation Rules from .flow/project-context.md if exists>
113119
"
114120
})
115121
```

flowctl/crates/flowctl-cli/src/commands/admin/guard.rs

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use serde_json::json;
1313

1414
use crate::output::{error_exit, json_output, pretty_output};
1515

16+
use flowctl_core::project_context::ProjectContext;
1617
use flowctl_core::types::CONFIG_FILE;
1718

1819
use super::{deep_merge, get_default_config, get_flow_dir};
@@ -337,10 +338,43 @@ pub fn cmd_guard(json_mode: bool, layer: String) {
337338
get_default_config()
338339
};
339340

341+
// ── Priority chain: project-context → stack config → auto-detection ──
342+
// Try project-context.md first (highest priority).
343+
let pc = ProjectContext::load(&flow_dir);
344+
let pc_commands = pc.as_ref().map(|ctx| {
345+
let gc = &ctx.guard_commands;
346+
let mut cmds: Vec<(String, String, String)> = Vec::new();
347+
let layer_name = "project".to_string();
348+
if let Some(ref cmd) = gc.test {
349+
if !cmd.is_empty() && (layer == "all" || layer == "project") {
350+
cmds.push((layer_name.clone(), "test".to_string(), cmd.clone()));
351+
}
352+
}
353+
if let Some(ref cmd) = gc.lint {
354+
if !cmd.is_empty() && (layer == "all" || layer == "project") {
355+
cmds.push((layer_name.clone(), "lint".to_string(), cmd.clone()));
356+
}
357+
}
358+
if let Some(ref cmd) = gc.typecheck {
359+
if !cmd.is_empty() && (layer == "all" || layer == "project") {
360+
cmds.push((layer_name.clone(), "typecheck".to_string(), cmd.clone()));
361+
}
362+
}
363+
if let Some(ref cmd) = gc.format_check {
364+
if !cmd.is_empty() && (layer == "all" || layer == "project") {
365+
cmds.push((layer_name.clone(), "format_check".to_string(), cmd.clone()));
366+
}
367+
}
368+
cmds
369+
});
370+
371+
// Use project-context commands if any are defined; otherwise fall back to stack config.
372+
let use_project_context = pc_commands.as_ref().is_some_and(|c| !c.is_empty());
373+
340374
let stack = config.get("stack").cloned().unwrap_or(json!({}));
341375
let stack_obj = stack.as_object();
342376

343-
if stack_obj.is_none() || stack_obj.unwrap().is_empty() {
377+
if !use_project_context && (stack_obj.is_none() || stack_obj.unwrap().is_empty()) {
344378
if json_mode {
345379
json_output(json!({
346380
"results": [],
@@ -355,20 +389,24 @@ pub fn cmd_guard(json_mode: bool, layer: String) {
355389
let cmd_types = ["test", "lint", "typecheck"];
356390
let mut commands: Vec<(String, String, String)> = Vec::new(); // (layer_name, type, cmd)
357391

358-
for (layer_name, layer_conf) in stack_obj.unwrap() {
359-
if layer != "all" && layer_name != &layer {
360-
continue;
361-
}
362-
if let Some(layer_obj) = layer_conf.as_object() {
363-
for ct in &cmd_types {
364-
if let Some(cmd_val) = layer_obj.get(*ct) {
365-
if let Some(cmd_str) = cmd_val.as_str() {
366-
if !cmd_str.is_empty() {
367-
commands.push((
368-
layer_name.clone(),
369-
ct.to_string(),
370-
cmd_str.to_string(),
371-
));
392+
if use_project_context {
393+
commands = pc_commands.unwrap();
394+
} else {
395+
for (layer_name, layer_conf) in stack_obj.unwrap() {
396+
if layer != "all" && layer_name != &layer {
397+
continue;
398+
}
399+
if let Some(layer_obj) = layer_conf.as_object() {
400+
for ct in &cmd_types {
401+
if let Some(cmd_val) = layer_obj.get(*ct) {
402+
if let Some(cmd_str) = cmd_val.as_str() {
403+
if !cmd_str.is_empty() {
404+
commands.push((
405+
layer_name.clone(),
406+
ct.to_string(),
407+
cmd_str.to_string(),
408+
));
409+
}
372410
}
373411
}
374412
}

0 commit comments

Comments
 (0)