feat(v0.6): AGENTS.md seed + CLAUDE.md shim + handoff + context-archive scaffold
New workspaces get:
- AGENTS.md (always): canonical agent instructions — handoff workflow,
notes-archive convention (~300-500 line trigger), cross-workspace
discovery, do-not-touch warnings. Project variant adds workspace-
structure and git-conventions sections.
- CLAUDE.md shim (claude type only per v0.6 spec §6 — opencode shim
deferred until its instruction-file convention is verified).
- handoff.md: minimal current-state template the agent updates per
session.
- context/notes-archive/.gitkeep: directory pinned for git tracking,
ready for the agent to populate per the archive convention.
seed.ClaudeMD and seed.ClaudeMDProject removed — no callers remain.
Existing workspaces are NOT modified; this is strictly a ctask-new code
path. The seed-wins overlay rule still applies — a user seed dir's
AGENTS.md/CLAUDE.md overrides the built-in.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package seed
|
||||
|
||||
// AgentsMD returns the canonical AGENTS.md content for a new workspace.
|
||||
// This is the single source of truth for agent-facing instructions in
|
||||
// v0.6+. Agent-specific files (CLAUDE.md, etc.) become thin shims
|
||||
// pointing back here. Per v0.6 spec §6.
|
||||
//
|
||||
// isProject controls a small set of project-specific additions
|
||||
// (workspace-structure section, git conventions).
|
||||
func AgentsMD(isProject bool) string {
|
||||
base := `# Workspace Instructions
|
||||
|
||||
This workspace uses ctask. AGENTS.md is the canonical instruction file
|
||||
for agents working here. Agent-specific files (CLAUDE.md, etc.) are
|
||||
thin shims that defer to this document.
|
||||
|
||||
## Session Workflow
|
||||
|
||||
- Read handoff.md first when starting a session. It carries the
|
||||
current-state briefing: last completed work, immediate next step,
|
||||
blockers, and files to inspect first.
|
||||
- Update notes.md with decisions, design rationale, and observations
|
||||
during the session.
|
||||
- Update handoff.md before ending the session so the next agent (or
|
||||
the next instance of you) can pick up cleanly. Keep it short and
|
||||
current -- handoff.md is not a history file.
|
||||
|
||||
## Notes Archival
|
||||
|
||||
When notes.md becomes too large to scan comfortably (roughly 300-500
|
||||
lines or 10-20 KB), archive older completed-phase sections:
|
||||
|
||||
1. Create a new file: context/notes-archive/YYYY-MM-DD-topic.md
|
||||
2. Move the older sections into it, preserving them exactly.
|
||||
3. Add a pointer at the top of notes.md:
|
||||
"Older notes archived in context/notes-archive/YYYY-MM-DD-topic.md"
|
||||
4. Do not delete or summarize-away historical notes as the only
|
||||
preservation mechanism.
|
||||
5. Keep handoff.md short and current. It is not a history file.
|
||||
|
||||
## Cross-Workspace Context
|
||||
|
||||
Related work may exist in other ctask workspaces. Use:
|
||||
|
||||
ctask list --all discover all workspaces, including archived
|
||||
ctask info <workspace> view metadata and status
|
||||
ctask notes <workspace> read another workspace's notes.md
|
||||
ctask path <workspace> get the filesystem path
|
||||
|
||||
Treat other workspaces as read-only unless the user explicitly asks
|
||||
otherwise.
|
||||
|
||||
## Do Not Touch
|
||||
|
||||
- Do not modify .ctask/ -- those are ctask internals (lease, manifests,
|
||||
session summary, write lock).
|
||||
- Do not edit task.yaml's metadata fields by hand. ctask owns them.
|
||||
`
|
||||
if !isProject {
|
||||
return base + `
|
||||
## File Placement (task workspace)
|
||||
|
||||
- Source code and scripts -> workspace root or src/
|
||||
- Documentation, summaries, reports -> docs/
|
||||
- Deliverables and exports -> output/
|
||||
- Reference material and imported files -> context/
|
||||
- Do not place non-code outputs in the workspace root.
|
||||
`
|
||||
}
|
||||
return base + `
|
||||
## Workspace Structure (project workspace)
|
||||
|
||||
This workspace uses ctask's project layout. The workspace root contains
|
||||
ctask management files; your project code lives in the project
|
||||
subdirectory (named after the workspace slug). When working on project
|
||||
code, operate inside the project subdirectory.
|
||||
|
||||
- Workspace root: ctask metadata, session logs, notes, reference material
|
||||
- Project subdirectory: source code, project-specific configuration
|
||||
- context/: reference material and imported specs (workspace level)
|
||||
- output/: deliverables and exports (workspace level)
|
||||
- logs/: session logs (managed by ctask)
|
||||
|
||||
## Git Conventions
|
||||
|
||||
This workspace uses a single git repository initialized at the workspace
|
||||
root. The project subdirectory and all its contents are tracked by this
|
||||
root repo. Do not initialize additional git repositories inside the
|
||||
project subdirectory or elsewhere -- the repo root is the workspace root.
|
||||
`
|
||||
}
|
||||
|
||||
// ClaudeShimMD returns the thin CLAUDE.md generated for new workspaces
|
||||
// whose agent.type is "claude". It exists purely to point Claude Code at
|
||||
// AGENTS.md; canonical instructions live there. Per v0.6 spec §6.
|
||||
func ClaudeShimMD() string {
|
||||
return `<!-- Generated by ctask. Edit AGENTS.md for canonical instructions.
|
||||
This file may be regenerated by future ctask agent-management commands. -->
|
||||
|
||||
# Claude Code Instructions
|
||||
|
||||
This workspace uses AGENTS.md as the canonical agent guidance file.
|
||||
Read AGENTS.md first.
|
||||
|
||||
## Claude-specific notes
|
||||
|
||||
- Use claude CLI conventions for file operations.
|
||||
- Respect the workspace structure described in AGENTS.md.
|
||||
`
|
||||
}
|
||||
|
||||
// HandoffMD returns the minimal handoff.md template seeded into new
|
||||
// workspaces. The agent fills it in at session end; the next session
|
||||
// reads it first. Per v0.6 spec §8.
|
||||
func HandoffMD() string {
|
||||
return `# Handoff
|
||||
|
||||
## Current state
|
||||
|
||||
New workspace. No work completed yet.
|
||||
|
||||
## Next step
|
||||
|
||||
[Describe the task or project goal here.]
|
||||
|
||||
## Files to read first
|
||||
|
||||
- AGENTS.md - workspace instructions and conventions
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package seed
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAgentsMDContainsHandoffWorkflow(t *testing.T) {
|
||||
body := AgentsMD(false)
|
||||
for _, want := range []string{
|
||||
"handoff.md", // mentions the handoff file
|
||||
"Read handoff.md first", // session-start convention
|
||||
"notes.md", // mentions notes
|
||||
"context/notes-archive/", // archive convention
|
||||
"300", // size hint
|
||||
"ctask list", // cross-workspace discovery
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("AGENTS.md missing %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentsMDProjectVariantHasGitConventions(t *testing.T) {
|
||||
body := AgentsMD(true)
|
||||
for _, want := range []string{
|
||||
"git", // git mentioned for projects
|
||||
"Workspace Structure", // project-specific section
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("AGENTS.md (project) missing %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeShimMDPointsAtAgentsMD(t *testing.T) {
|
||||
body := ClaudeShimMD()
|
||||
for _, want := range []string{
|
||||
"Generated by ctask",
|
||||
"AGENTS.md",
|
||||
"Read AGENTS.md first",
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("CLAUDE.md shim missing %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandoffMDIsMinimalTemplate(t *testing.T) {
|
||||
body := HandoffMD()
|
||||
for _, want := range []string{
|
||||
"# Handoff",
|
||||
"Current state",
|
||||
"Next step",
|
||||
"AGENTS.md",
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("handoff.md missing %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentsMDIsASCII(t *testing.T) {
|
||||
for _, body := range []string{AgentsMD(false), AgentsMD(true), ClaudeShimMD(), HandoffMD()} {
|
||||
for i, b := range []byte(body) {
|
||||
if b > 127 {
|
||||
t.Errorf("non-ASCII byte 0x%02x at position %d", b, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,131 +2,6 @@ package seed
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ClaudeMD returns the built-in default CLAUDE.md content for a task workspace.
|
||||
// Parameters are accepted for API compatibility but are not interpolated in v0.3.
|
||||
func ClaudeMD(slug, category, workspacePath string) string {
|
||||
_ = slug
|
||||
_ = category
|
||||
_ = workspacePath
|
||||
return `# Workspace Guidelines
|
||||
|
||||
This is a ctask workspace. Prefer operating inside this directory unless explicitly instructed otherwise.
|
||||
|
||||
## File Placement
|
||||
|
||||
- Source code and scripts -> workspace root or ` + "`src/`" + `
|
||||
- Documentation, summaries, reports -> ` + "`docs/`" + `
|
||||
- Deliverables and exports -> ` + "`output/`" + `
|
||||
- Reference material and imported files -> ` + "`context/`" + `
|
||||
- Do not place non-code outputs (docs, summaries, exports) in the workspace root
|
||||
|
||||
## Conventions
|
||||
|
||||
- Do not install global packages or modify system files unless asked
|
||||
- Record important assumptions and actions in notes.md
|
||||
- Keep the workspace root clean -- use subdirectories for organization
|
||||
|
||||
## Cross-Workspace Context
|
||||
|
||||
Related work may exist in other ctask workspaces. For project continuation,
|
||||
migration, debugging, or building on prior work, inspect related workspaces
|
||||
before making changes.
|
||||
|
||||
Available commands:
|
||||
|
||||
ctask list --all discover all workspaces, including archived
|
||||
ctask info <workspace> view metadata and status of any workspace
|
||||
ctask notes <workspace> read another workspace's notes.md
|
||||
ctask path <workspace> get the filesystem path to inspect files directly
|
||||
|
||||
Treat other workspaces as read-only unless the user explicitly asks you to
|
||||
modify them.
|
||||
|
||||
## Session Handoff
|
||||
|
||||
Before ending a session, append a brief summary to notes.md with:
|
||||
|
||||
- What was accomplished
|
||||
- Key decisions made
|
||||
- Open follow-ups or unfinished work
|
||||
- How to continue from here
|
||||
|
||||
Keep it concise -- a few bullet points is enough.
|
||||
`
|
||||
}
|
||||
|
||||
// ClaudeMDProject returns the built-in default CLAUDE.md content for a project workspace.
|
||||
func ClaudeMDProject() string {
|
||||
return `# Project Workspace Guidelines
|
||||
|
||||
This is a ctask project workspace -- a long-lived working environment, not a disposable task.
|
||||
|
||||
## Workspace Structure
|
||||
|
||||
This is a ctask project workspace. The workspace root contains ctask management
|
||||
files. Your project code lives in the project subdirectory.
|
||||
|
||||
- Workspace root: ctask metadata, session logs, notes, reference material
|
||||
- Project subdirectory: source code, project CLAUDE.md, project configuration
|
||||
- ` + "`context/`" + `: reference material and imported specs (workspace level)
|
||||
- ` + "`output/`" + `: deliverables and exports (workspace level)
|
||||
- ` + "`logs/`" + `: session logs (managed by ctask)
|
||||
|
||||
When working on project code, operate inside the project subdirectory.
|
||||
Place project-specific CLAUDE.md, documentation, and configuration there.
|
||||
|
||||
## File Placement
|
||||
|
||||
- Source code -> ` + "`src/`" + ` (inside the project subdirectory)
|
||||
- Documentation -> ` + "`docs/`" + ` (workspace level for general notes, project subdir for project docs)
|
||||
- Deliverables and exports -> ` + "`output/`" + ` (workspace level)
|
||||
- Reference material -> ` + "`context/`" + ` (workspace level)
|
||||
- Tests -> ` + "`tests/`" + ` (inside the project subdirectory)
|
||||
- Configuration files -> inside the project subdirectory
|
||||
- Do not place non-code outputs in the workspace root
|
||||
|
||||
## Conventions
|
||||
|
||||
- This project uses git. Commit meaningful changes with clear messages.
|
||||
- Do not install global packages or modify system files unless asked.
|
||||
- Record important assumptions and actions in notes.md (workspace level).
|
||||
- Keep the workspace root clean.
|
||||
|
||||
## Git
|
||||
|
||||
This workspace uses a single git repository initialized at the workspace root.
|
||||
The project subdirectory and all its contents are tracked by this root repo.
|
||||
Do not initialize additional git repositories inside the project subdirectory
|
||||
or any other subdirectory. If you need to check git status or make commits,
|
||||
the repo root is the workspace root.
|
||||
|
||||
## Cross-Workspace Context
|
||||
|
||||
Related work may exist in other ctask workspaces. For project continuation,
|
||||
migration, debugging, or building on prior work, inspect related workspaces
|
||||
before making changes.
|
||||
|
||||
Available commands:
|
||||
|
||||
ctask list --all discover all workspaces, including archived
|
||||
ctask info <workspace> view metadata and status of any workspace
|
||||
ctask notes <workspace> read another workspace's notes.md
|
||||
ctask path <workspace> get the filesystem path to inspect files directly
|
||||
|
||||
Treat other workspaces as read-only unless the user explicitly asks you to
|
||||
modify them.
|
||||
|
||||
## Session Handoff
|
||||
|
||||
Before ending a session, append a brief summary to notes.md with:
|
||||
|
||||
- What was accomplished
|
||||
- Key decisions made
|
||||
- Open follow-ups or unfinished work
|
||||
- How to continue from here
|
||||
`
|
||||
}
|
||||
|
||||
// NotesMD returns the skeleton notes.md content.
|
||||
func NotesMD(title string) string {
|
||||
return fmt.Sprintf(`# %s
|
||||
|
||||
@@ -1,128 +1,9 @@
|
||||
package seed
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClaudeMDIsASCII(t *testing.T) {
|
||||
content := ClaudeMD("test-slug", "general", "/tmp/test")
|
||||
for i, b := range []byte(content) {
|
||||
if b > 127 {
|
||||
t.Errorf("non-ASCII byte 0x%02x at position %d in CLAUDE.md template", b, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeMDContainsV03Sections(t *testing.T) {
|
||||
content := ClaudeMD("ignored", "ignored", "ignored")
|
||||
for _, want := range []string{
|
||||
"# Workspace Guidelines",
|
||||
"## File Placement",
|
||||
"Source code and scripts",
|
||||
"Documentation, summaries, reports",
|
||||
"Deliverables and exports",
|
||||
"Reference material and imported files",
|
||||
"## Conventions",
|
||||
"## Session Handoff",
|
||||
"What was accomplished",
|
||||
} {
|
||||
if !strings.Contains(content, want) {
|
||||
t.Errorf("CLAUDE.md missing required section: %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeMDProjectIsASCII(t *testing.T) {
|
||||
content := ClaudeMDProject()
|
||||
for i, b := range []byte(content) {
|
||||
if b > 127 {
|
||||
t.Errorf("non-ASCII byte 0x%02x at position %d in project CLAUDE.md template", b, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeMDProjectContainsRequiredSections(t *testing.T) {
|
||||
content := ClaudeMDProject()
|
||||
for _, want := range []string{
|
||||
"# Project Workspace Guidelines",
|
||||
"long-lived working environment",
|
||||
"## File Placement",
|
||||
"Source code -> ",
|
||||
"Tests -> ",
|
||||
"## Conventions",
|
||||
"This project uses git",
|
||||
"## Session Handoff",
|
||||
} {
|
||||
if !strings.Contains(content, want) {
|
||||
t.Errorf("project CLAUDE.md missing required section: %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeMDProjectContainsNestedGitRule(t *testing.T) {
|
||||
body := ClaudeMDProject()
|
||||
for _, must := range []string{
|
||||
"single git repository",
|
||||
"Do not initialize additional git repositories",
|
||||
} {
|
||||
if !strings.Contains(body, must) {
|
||||
t.Errorf("ClaudeMDProject missing guidance %q", must)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeMDProjectDescribesWorkspaceStructure(t *testing.T) {
|
||||
body := ClaudeMDProject()
|
||||
for _, must := range []string{
|
||||
"## Workspace Structure",
|
||||
"Workspace root: ctask metadata",
|
||||
"Project subdirectory",
|
||||
"project CLAUDE.md",
|
||||
"operate inside the project subdirectory",
|
||||
} {
|
||||
if !strings.Contains(body, must) {
|
||||
t.Errorf("project CLAUDE.md missing marker %q", must)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeMDContainsCrossWorkspaceSection(t *testing.T) {
|
||||
// v0.5.2: both templates teach agents to use ctask's read-only context
|
||||
// commands before starting work.
|
||||
content := ClaudeMD("ignored", "ignored", "ignored")
|
||||
for _, must := range []string{
|
||||
"## Cross-Workspace Context",
|
||||
"ctask list --all",
|
||||
"ctask info <workspace>",
|
||||
"ctask notes <workspace>",
|
||||
"ctask path <workspace>",
|
||||
"Treat other workspaces as read-only",
|
||||
} {
|
||||
if !strings.Contains(content, must) {
|
||||
t.Errorf("task CLAUDE.md missing cross-workspace marker %q", must)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeMDProjectContainsCrossWorkspaceSection(t *testing.T) {
|
||||
content := ClaudeMDProject()
|
||||
for _, must := range []string{
|
||||
"## Cross-Workspace Context",
|
||||
"ctask list --all",
|
||||
"ctask info <workspace>",
|
||||
"ctask notes <workspace>",
|
||||
"ctask path <workspace>",
|
||||
"Treat other workspaces as read-only",
|
||||
} {
|
||||
if !strings.Contains(content, must) {
|
||||
t.Errorf("project CLAUDE.md missing cross-workspace marker %q", must)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotesMDIsASCII(t *testing.T) {
|
||||
content := NotesMD("test title")
|
||||
for i, b := range []byte(content) {
|
||||
|
||||
Reference in New Issue
Block a user