workflow: polish — /context groups trackers, /workflow in README, memoize git root

- contextPanel splits 'instructions' from 'trackers' (TODO.md/ROADMAP.md), so
  /context no longer presents project trackers as standing orders, and its
  empty-state mentions both instruction files and trackers
- README: docs table gets Project workflow, command list gets the new commands
- session: gitRoot memoized (one sync fs walk per session, not per prompt
  build); workflowPolicy computed once per systemFor
- test: context panel grouping covered

10 workflow tests, 830+ suite green
This commit is contained in:
asepharyana
2026-09-09 19:12:29 +07:00
parent a3739eb628
commit 4aeb0d2455
4 changed files with 43 additions and 12 deletions
+15 -3
View File
@@ -423,18 +423,29 @@ export class Session {
* Node's existsSync over Bun.file(...).exists().
*/
private gitRoot(): string | undefined {
if (this.gitRootResolved) return this.cachedGitRoot ?? undefined;
this.gitRootResolved = true;
try {
let dir = resolve(this.opts.cwd ?? process.cwd());
while (true) {
if (existsSync(join(dir, '.git', 'HEAD'))) return dir;
if (existsSync(join(dir, '.git', 'HEAD'))) {
this.cachedGitRoot = dir;
return dir;
}
const parent = dirname(dir);
if (parent === dir) return undefined;
if (parent === dir) {
this.cachedGitRoot = null;
return undefined;
}
dir = parent;
}
} catch {
this.cachedGitRoot = null;
return undefined;
}
}
private cachedGitRoot: string | null | undefined;
private gitRootResolved = false;
/**
* Rendered only when the project tracks its own progress (TODO.md/ROADMAP.md
@@ -763,6 +774,7 @@ export class Session {
const mem = this.opts.memory;
const memoryBlock = mem ? mem.render() : '';
const workflowPolicy = this.workflowPolicy();
const text = systemPrompt({
cwd: this.opts.cwd ?? process.cwd(),
instructions: this.opts.instructions ?? [],
@@ -775,7 +787,7 @@ export class Session {
canAsk: this.opts.ask !== undefined && this.activeTools().includes('ask'),
...(this.mcpServerNamesForPrompt() ? { mcpServers: this.mcpServerNamesForPrompt() } : {}),
...(this.workspaceFiles && this.workspaceFiles.length > 0 ? { workspaceFiles: this.workspaceFiles } : {}),
...(this.workflowPolicy() ? { workflowPolicy: this.workflowPolicy() } : {}),
...(workflowPolicy ? { workflowPolicy } : {}),
});
this.promptCache = { key: versionKey, text };
return text;
+10 -7
View File
@@ -72,13 +72,16 @@ export function costPanel(
}
export function contextPanel(files: readonly string[]): Panel {
return {
title: 'project instructions',
body:
files.length > 0
? files.map((f) => `- \`${f}\``).join('\n')
: 'No `AGENTS.md`, `CLAUDE.md`, or `.shiro.md` found. Run `/init` to write one.',
};
const tracker = files.filter((f) => f.endsWith('TODO.md') || f.endsWith('ROADMAP.md'));
const instructions = files.filter((f) => !tracker.includes(f));
const body: string[] = [];
if (instructions.length > 0) body.push('instructions:', ...instructions.map((f) => `- \`${f}\``));
if (tracker.length > 0) body.push('trackers:', ...tracker.map((f) => `- \`${f}\``));
if (body.length === 0) {
body.push('No `AGENTS.md`, `CLAUDE.md`, or `.shiro.md` found. Run `/init` to write one.');
body.push('No `TODO.md` or `ROADMAP.md` found — no project tracker is loaded.');
}
return { title: 'project instructions & trackers', body: body.join('\n') };
}
export const todosPanel = (session: Session): Panel => ({