Repository: JamesWuHK/agent-loop
Author: JamesWuHK
## 用户故事
作为 `agent-loop` 维护者,我希望每轮 issue attempt 都先落一份本地 durable 的 `Sprint Contract`,从而让 planner、executor、evaluator、recovery 在同一轮里共享“这一轮准备做什么、怎么判 done”的 contract,而不是只靠 issue body 和日志。
## Context
### Dependencies
```json
{
"dependsOn": []
}
```
### Constraints
- 这一版只建立 `Sprint Contract` 与 attempt artifact schema,不在这里接 planner / executor / daemon 主流程
- `Sprint Contract` 是从 issue contract 派生出的本地 artifact,不替代 GitHub issue body,也不写回远端
- artifact 格式必须 deterministic,缺文件时要能安全降级,不允许因为缺少本地 artifact 就让 daemon 崩掉
### RuntimeRequirements
- self-hosting
- managed-runtime
### AllowedFiles
- apps/agent-daemon/src/sprint-contract.ts
- apps/agent-daemon/src/sprint-contract.test.ts
- packages/agent-shared/src/types.ts
- packages/agent-shared/src/index.ts
- README.md
- docs/roadmaps/README.md
- docs/roadmaps/issue-harness-phase-1/README.md
### ForbiddenFiles
- apps/agent-daemon/src/daemon.ts
- apps/agent-daemon/src/subtask-executor.ts
- apps/agent-daemon/src/ready-gate.ts
- apps/agent-daemon/src/issue-simulate.ts
### MustPreserve
- GitHub issue body 仍然是任务语义的权威来源;`Sprint Contract` 只能是本地派生 snapshot
- 本次切片不能引入新的 GitHub side effect,不能创建 comment、label 或 remote metadata
- 缺少 `.agent-loop` artifact 时,后续代码必须能够显式返回 `null` 或 empty state,而不是抛异常
### OutOfScope
- planner / executor integration
- unified evaluator loop
- recovery / resume prompt 改造
- dashboard / status / replay 可视化
### RequiredSemantics
- 新增 `SprintContract` 类型,至少包含:
- `artifactVersion`
- `issueNumber`
- `issueTitle`
- `attemptKind`
- `objective`
- `allowedFiles`
- `requiredSemantics`
- `validationCommands`
- `plannedSteps`
- `createdAt`
- `attemptKind` 首批固定支持:
- `fresh-claim`
- `issue-recovery`
- `review-auto-fix`
- 新增本地 helper,用于读写 worktree 内的 `.agent-loop/sprint-contract.json`
- serializer 必须输出稳定 JSON;同样输入不能因为 key 顺序或空字段出现 nondeterministic diff
- `README` 和 roadmap 要把 `Sprint Contract` 定义为 issue harness 的本地 contract,而不是新的远端 issue schema
### ReviewHints
- 优先检查 `Sprint Contract` 是否真的保持“本地派生 artifact”定位,而不是偷偷变成第二套远端 contract
- 优先检查 read helper 的缺文件行为,避免 recovery path 因为 artifact 缺失而崩掉
- 优先检查字段是不是围绕 execution harness 最小闭环,而不是一次塞入未来 app evaluator 的所有需求
### Validation
- `bun test apps/agent-daemon/src/sprint-contract.test.ts`
- `bun run typecheck`
- `git diff --stat origin/master...HEAD`
## RED 测试
```ts
import { describe, expect, test } from 'bun:test'
import { mkdtempSync, rmSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import {
buildSprintContract,
readSprintContract,
writeSprintContract,
} from './sprint-contract'
describe('SprintContract artifacts', () => {
test('round-trips a sprint contract through worktree-local artifact storage', async () => {
const worktreePath = mkdtempSync(join(tmpdir(), 'agent-loop-sprint-contract-'))
try {
const contract = buildSprintContract({
issueNumber: 34,
issueTitle: '[AL-34] sprint contract',
attemptKind: 'fresh-claim',
objective: 'Introduce a durable attempt contract before the first writable run',
allowedFiles: ['apps/agent-daemon/src/sprint-contract.ts'],
requiredSemantics: ['issue body remains the source of truth'],
validationCommands: ['bun test apps/agent-daemon/src/sprint-contract.test.ts'],
plannedSteps: ['add artifact helpers', 'add round-trip coverage'],
})
await writeSprintContract(worktreePath, contract)
expect(await readSprintContract(worktreePath)).toEqual(contract)
} finally {
rmSync(worktreePath, { recursive: true, force: true })
}
})
})
```
## 实现步骤
1. 先在 daemon 侧新增 `sprint-contract` 模块与本地 artifact IO helper
2. 再把 shared runtime types 暴露出去,固定 `SprintContract` 的最小字段集与 `attemptKind` vocabulary
3. 最后补 README / roadmap 文档,让后续 issue 能围绕这份 schema 接 planner / evaluator / recovery
## 验收
- [ ] 只修改 `AllowedFiles` 内文件
- [ ] `MustPreserve` 行为未回归
- [ ] `OutOfScope` 内容未混入
- [ ] RED 测试转绿
- [ ] 完成 `Validation` 中要求的验证