agent-project-sdlc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/README.md +5 -0
- package/assets/agents/.gitkeep +1 -0
- package/assets/agents/AGENTS_CORE.md +164 -0
- package/assets/github/.gitkeep +1 -0
- package/assets/github/harness.yml +45 -0
- package/assets/make/.gitkeep +1 -0
- package/assets/make/sdlc-harness.mk +78 -0
- package/assets/policies/allowed_paths.yaml +55 -0
- package/assets/policies/gates.yaml +48 -0
- package/assets/policies/phase_contracts.yaml +140 -0
- package/assets/policies/risk_matrix.yaml +27 -0
- package/assets/skills/architect_design/SKILL.md +62 -0
- package/assets/skills/dev_sprint/SKILL.md +90 -0
- package/assets/skills/implementation_doc/SKILL.md +58 -0
- package/assets/skills/manager/SKILL.md +50 -0
- package/assets/skills/pm_prd/SKILL.md +59 -0
- package/assets/skills/release_manager/SKILL.md +59 -0
- package/assets/skills/reviewer/SKILL.md +60 -0
- package/assets/skills/rfc_recalibrate/SKILL.md +61 -0
- package/assets/skills/tester/SKILL.md +59 -0
- package/assets/templates/ADR_TEMPLATE.md +19 -0
- package/assets/templates/IMPLEMENTATION_DOC_TEMPLATE.md +53 -0
- package/assets/templates/PLAN_TEMPLATE.yaml +25 -0
- package/assets/templates/PRD_TEMPLATE.md +41 -0
- package/assets/templates/RELEASE_TEMPLATE.md +34 -0
- package/assets/templates/REVIEW_TEMPLATE.md +31 -0
- package/assets/templates/RFC_TEMPLATE.md +36 -0
- package/assets/templates/SKILL_TEMPLATE.md +52 -0
- package/assets/templates/TECH_DESIGN_TEMPLATE.md +54 -0
- package/assets/templates/TEST_PLAN_TEMPLATE.md +29 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +12 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +16 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +30 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +56 -0
- package/dist/commands/package-source.d.ts +1 -0
- package/dist/commands/package-source.js +24 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +11 -0
- package/dist/commands/upgrade.d.ts +1 -0
- package/dist/commands/upgrade.js +7 -0
- package/dist/commands/validate.d.ts +1 -0
- package/dist/commands/validate.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lib/config.d.ts +5 -0
- package/dist/lib/config.js +55 -0
- package/dist/lib/doctor.d.ts +6 -0
- package/dist/lib/doctor.js +31 -0
- package/dist/lib/fs.d.ts +8 -0
- package/dist/lib/fs.js +56 -0
- package/dist/lib/harness-root.d.ts +9 -0
- package/dist/lib/harness-root.js +50 -0
- package/dist/lib/init.d.ts +5 -0
- package/dist/lib/init.js +76 -0
- package/dist/lib/managed-file.d.ts +6 -0
- package/dist/lib/managed-file.js +6 -0
- package/dist/lib/migrations.d.ts +12 -0
- package/dist/lib/migrations.js +176 -0
- package/dist/lib/package-json-config.d.ts +2 -0
- package/dist/lib/package-json-config.js +37 -0
- package/dist/lib/package-source.d.ts +8 -0
- package/dist/lib/package-source.js +107 -0
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +11 -0
- package/dist/lib/sync-engine.d.ts +7 -0
- package/dist/lib/sync-engine.js +202 -0
- package/dist/lib/types.d.ts +22 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/upgrade.d.ts +1 -0
- package/dist/lib/upgrade.js +16 -0
- package/dist/lib/validators.d.ts +6 -0
- package/dist/lib/validators.js +158 -0
- package/dist/lib/yaml.d.ts +2 -0
- package/dist/lib/yaml.js +7 -0
- package/migrations/README.md +3 -0
- package/package.json +31 -0
- package/source-mappings.yaml +19 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Release Note And Rollback Plan(发布说明与回滚方案)
|
|
2
|
+
|
|
3
|
+
## 1. Release Summary(发布摘要)
|
|
4
|
+
|
|
5
|
+
- Version:
|
|
6
|
+
- Milestone:
|
|
7
|
+
- Date:
|
|
8
|
+
- Owner:
|
|
9
|
+
|
|
10
|
+
## 2. Included Changes(包含变更)
|
|
11
|
+
|
|
12
|
+
-
|
|
13
|
+
|
|
14
|
+
## 3. Build Artifacts(构建产物)
|
|
15
|
+
|
|
16
|
+
| 产物(Artifact) | 位置(Location) | Checksum/Version |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| | | |
|
|
19
|
+
|
|
20
|
+
## 4. Smoke Test Result(冒烟测试结果)
|
|
21
|
+
|
|
22
|
+
- Decision: `PASS` / `BLOCKED`
|
|
23
|
+
- Evidence:
|
|
24
|
+
|
|
25
|
+
## 5. Deployment Checklist(部署检查清单)
|
|
26
|
+
|
|
27
|
+
- [ ]
|
|
28
|
+
|
|
29
|
+
## 6. Rollback Plan(回滚方案)
|
|
30
|
+
|
|
31
|
+
- 触发条件(Trigger):
|
|
32
|
+
- 步骤(Steps):
|
|
33
|
+
- 数据注意事项(Data considerations):
|
|
34
|
+
- 负责人(Owner):
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Review Report(评审报告)
|
|
2
|
+
|
|
3
|
+
## 1. Review 范围
|
|
4
|
+
|
|
5
|
+
- PRD:
|
|
6
|
+
- 技术方案(Technical design):
|
|
7
|
+
- 实现文档(Implementation docs):
|
|
8
|
+
- Diff/commit:
|
|
9
|
+
|
|
10
|
+
## 2. Findings(发现与风险)
|
|
11
|
+
|
|
12
|
+
| 严重程度(Severity) | 文件/区域(File/Area) | 发现(Finding) | 建议(Recommendation) |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| P0/P1/P2 | | | |
|
|
15
|
+
|
|
16
|
+
## 3. 需求一致性
|
|
17
|
+
|
|
18
|
+
-
|
|
19
|
+
|
|
20
|
+
## 4. 架构与可维护性
|
|
21
|
+
|
|
22
|
+
-
|
|
23
|
+
|
|
24
|
+
## 5. Test Gaps(测试缺口)
|
|
25
|
+
|
|
26
|
+
-
|
|
27
|
+
|
|
28
|
+
## 6. Gate Result(阶段结论)
|
|
29
|
+
|
|
30
|
+
- Decision: `PASS` / `BLOCKED`
|
|
31
|
+
- Required before testing:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# RFC_001: [Change Title]
|
|
2
|
+
|
|
3
|
+
## 1. 背景
|
|
4
|
+
|
|
5
|
+
说明为什么需要这次变更。
|
|
6
|
+
|
|
7
|
+
## 2. 变更内容(Change Content)
|
|
8
|
+
|
|
9
|
+
- Added:
|
|
10
|
+
- Changed:
|
|
11
|
+
- Removed:
|
|
12
|
+
- Unchanged:
|
|
13
|
+
|
|
14
|
+
## 3. Product Impact(产品影响)
|
|
15
|
+
|
|
16
|
+
| 受影响 PRD(Affected PRD) | 影响(Impact) |
|
|
17
|
+
|---|---|
|
|
18
|
+
| | |
|
|
19
|
+
|
|
20
|
+
## 4. Technical Impact Candidates(技术影响候选)
|
|
21
|
+
|
|
22
|
+
| 模块(Module) | 影响(Impact) | 置信度(Confidence) |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| | | low/medium/high |
|
|
25
|
+
|
|
26
|
+
## 5. Acceptance Criteria
|
|
27
|
+
|
|
28
|
+
- [ ]
|
|
29
|
+
|
|
30
|
+
## 6. Regression Requirements(回归要求)
|
|
31
|
+
|
|
32
|
+
- [ ]
|
|
33
|
+
|
|
34
|
+
## 7. Status
|
|
35
|
+
|
|
36
|
+
- Status: DRAFT
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: example_skill
|
|
3
|
+
description: Use when ...
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Example Skill
|
|
7
|
+
|
|
8
|
+
## 目的
|
|
9
|
+
|
|
10
|
+
用中文说明这个 Skill 的职责。保留 `name`、`description`、路径、命令、字段名和枚举的英文精确标识符。
|
|
11
|
+
|
|
12
|
+
## 角色提示词
|
|
13
|
+
|
|
14
|
+
用中文描述该角色在对话中的专业姿态、澄清方式、关键取舍和阶段产物生成责任。这里不是复述输入输出,而是说明 Agent 应如何和用户一起把模糊目标推进到可验证交付物。
|
|
15
|
+
|
|
16
|
+
角色提示词应保持通用,不绑定具体业务项目;如果必须依赖项目事实,应要求读取 `.docs/`、`<harnessRoot>/state/**` 或当前 task contract,而不是写入固定业务假设。
|
|
17
|
+
|
|
18
|
+
## 输入
|
|
19
|
+
|
|
20
|
+
- `<harnessRoot>/state/lifecycle.yaml`
|
|
21
|
+
- `<harnessRoot>/state/plan.yaml`
|
|
22
|
+
- 其它阶段需要读取的事实源
|
|
23
|
+
|
|
24
|
+
## 输出
|
|
25
|
+
|
|
26
|
+
- 该 Skill 负责生成或更新的产物路径
|
|
27
|
+
- 需要更新的状态文件或索引文件
|
|
28
|
+
- 如有 `.docs/` slice 变化,刷新对应 `overview.md`
|
|
29
|
+
|
|
30
|
+
## 语义切片
|
|
31
|
+
|
|
32
|
+
- 说明该 Skill 负责的文档目录按什么语义边界切片。
|
|
33
|
+
- 说明什么时候更新原 slice,什么时候新增、拆分、合并或废弃 slice。
|
|
34
|
+
- 如果该 Skill 不直接生成文档,说明它如何调用下游 Skill 或报告边界变化。
|
|
35
|
+
- 每次切片边界变化后,都要更新 `.docs/INDEX.md`。
|
|
36
|
+
- `overview.md` 是 generated artifact,不手写;slice 变化后运行 `make docs-overview`。
|
|
37
|
+
|
|
38
|
+
## 规则
|
|
39
|
+
|
|
40
|
+
1. 中文解释规则,英文标识符保持原样,例如 `current_phase`、`active_skill`、`required_gates`。
|
|
41
|
+
2. 命令保持原样,例如 `make validate-current`。
|
|
42
|
+
3. 阶段和状态枚举保持原样,例如 `SPRINTING`、`done`、`pending_revision`。
|
|
43
|
+
4. 如果该 Skill 执行 open task,说明 `plan.yaml` 中的 `allowed_paths` 和 `required_gates` 如何约束执行范围,并调用对应 gate。
|
|
44
|
+
|
|
45
|
+
## 完成检查
|
|
46
|
+
|
|
47
|
+
- [ ] 产物已写入约定路径。
|
|
48
|
+
- [ ] 已判断语义切片边界。
|
|
49
|
+
- [ ] 如当前 task 是 open task,`plan.yaml` 中的执行合同已完整。
|
|
50
|
+
- [ ] 如有 `.docs/` slice 变化,已运行 `make docs-overview`。
|
|
51
|
+
- [ ] 相关英文机器契约未被翻译。
|
|
52
|
+
- [ ] 对应 gate 准备通过。
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# [Feature/Capability] Technical Design
|
|
2
|
+
|
|
3
|
+
## 1. 关联产品需求
|
|
4
|
+
|
|
5
|
+
- PRD:
|
|
6
|
+
- Requirement IDs:
|
|
7
|
+
|
|
8
|
+
## 2. 现有上下文
|
|
9
|
+
|
|
10
|
+
- 当前模块(Current modules):
|
|
11
|
+
- 相关 APIs(Related APIs):
|
|
12
|
+
- 相关数据(Related data):
|
|
13
|
+
|
|
14
|
+
## 3. 方案架构
|
|
15
|
+
|
|
16
|
+
- 领域边界(Domain boundary):
|
|
17
|
+
- 主要组件(Main components):
|
|
18
|
+
- 数据流(Data flow):
|
|
19
|
+
|
|
20
|
+
```txt
|
|
21
|
+
Input
|
|
22
|
+
-> Validation
|
|
23
|
+
-> Core processing
|
|
24
|
+
-> Persistence / side effects
|
|
25
|
+
-> Output
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 4. 接口契约(Interface Contract)
|
|
29
|
+
|
|
30
|
+
| 接口(Interface) | 方法/事件(Method/Event) | 请求(Request) | 响应(Response) | 错误(Errors) |
|
|
31
|
+
|---|---|---|---|---|
|
|
32
|
+
| | | | | |
|
|
33
|
+
|
|
34
|
+
## 5. 数据模型(Data Model)
|
|
35
|
+
|
|
36
|
+
| 实体(Entity) | 字段(Fields) | 负责人(Owner) | 备注(Notes) |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| | | | |
|
|
39
|
+
|
|
40
|
+
## 6. 任务拆分(Task Breakdown)
|
|
41
|
+
|
|
42
|
+
| Task ID | 标题(Title) | Allowed Paths | Required Gates | Implementation Doc |
|
|
43
|
+
|---|---|---|---|---|
|
|
44
|
+
| DEV-001 | | `src/**`, `tests/**` | `make lint`, `make test-current-domain` | `.docs/04_implementation/...` |
|
|
45
|
+
|
|
46
|
+
## 7. 风险与缓解
|
|
47
|
+
|
|
48
|
+
| 风险(Risk) | 等级(Level) | 缓解措施(Mitigation) |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| | P1 | |
|
|
51
|
+
|
|
52
|
+
## 8. 需要关注的方案偏移
|
|
53
|
+
|
|
54
|
+
-
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Test Plan(测试计划)
|
|
2
|
+
|
|
3
|
+
## 1. Scope(范围)
|
|
4
|
+
|
|
5
|
+
- PRD:
|
|
6
|
+
- Technical design:
|
|
7
|
+
- Implementation docs:
|
|
8
|
+
- Review report:
|
|
9
|
+
|
|
10
|
+
## 2. Test Matrix(测试矩阵)
|
|
11
|
+
|
|
12
|
+
| 需求(Requirement) | 场景(Scenario) | 测试类型(Test Type) | 测试用例(Test Case) | 结果(Result) |
|
|
13
|
+
|---|---|---|---|---|
|
|
14
|
+
| | | unit/integration/e2e/regression | | pending |
|
|
15
|
+
|
|
16
|
+
## 3. Regression Checklist(回归检查清单)
|
|
17
|
+
|
|
18
|
+
- [ ]
|
|
19
|
+
|
|
20
|
+
## 4. Coverage Gaps(覆盖缺口)
|
|
21
|
+
|
|
22
|
+
| 缺口(Gap) | 风险(Risk) | 后续动作(Follow-up) |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| | | |
|
|
25
|
+
|
|
26
|
+
## 5. Final Result(最终结论)
|
|
27
|
+
|
|
28
|
+
- Decision: `PASS` / `BLOCKED`
|
|
29
|
+
- Evidence:
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { commands } from "./commands/index.js";
|
|
3
|
+
const [command = "help", ...args] = process.argv.slice(2);
|
|
4
|
+
async function main() {
|
|
5
|
+
const handler = commands[command] ?? commands.help;
|
|
6
|
+
await handler(args);
|
|
7
|
+
}
|
|
8
|
+
main().catch((error) => {
|
|
9
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10
|
+
console.error(`sdlc-harness: ${message}`);
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function doctor(): Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { runDoctor } from "../lib/doctor.js";
|
|
2
|
+
export async function doctor() {
|
|
3
|
+
const report = await runDoctor(process.cwd());
|
|
4
|
+
for (const line of report.info) {
|
|
5
|
+
console.log(line);
|
|
6
|
+
}
|
|
7
|
+
for (const warning of report.warnings) {
|
|
8
|
+
console.warn(`warning: ${warning}`);
|
|
9
|
+
}
|
|
10
|
+
for (const error of report.errors) {
|
|
11
|
+
console.error(`error: ${error}`);
|
|
12
|
+
}
|
|
13
|
+
if (report.errors.length > 0) {
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { doctor } from "./doctor.js";
|
|
2
|
+
import { init } from "./init.js";
|
|
3
|
+
import { packageSource } from "./package-source.js";
|
|
4
|
+
import { sync } from "./sync.js";
|
|
5
|
+
import { upgrade } from "./upgrade.js";
|
|
6
|
+
import { validate } from "./validate.js";
|
|
7
|
+
export const commands = {
|
|
8
|
+
help,
|
|
9
|
+
init,
|
|
10
|
+
sync,
|
|
11
|
+
upgrade,
|
|
12
|
+
doctor,
|
|
13
|
+
validate,
|
|
14
|
+
"validate-harness": (args) => validate(["validate-harness", ...args]),
|
|
15
|
+
"validate-current": (args) => validate(["validate-current", ...args]),
|
|
16
|
+
"validate-pm": (args) => validate(["validate-pm", ...args]),
|
|
17
|
+
"validate-design": (args) => validate(["validate-design", ...args]),
|
|
18
|
+
"validate-dev": (args) => validate(["validate-dev", ...args]),
|
|
19
|
+
package: packageSource
|
|
20
|
+
};
|
|
21
|
+
export function help() {
|
|
22
|
+
console.log(`sdlc-harness commands:
|
|
23
|
+
init [--adopt] [--harness-folder <path>]
|
|
24
|
+
Initialize a new project or adopt an existing one
|
|
25
|
+
sync Materialize canonical assets into the workspace
|
|
26
|
+
upgrade Run migrations and then sync
|
|
27
|
+
doctor Diagnose project configuration and drift
|
|
28
|
+
validate <gate> Run a Harness validation gate
|
|
29
|
+
package <subcommand> Maintain package canonical source`);
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
2
|
+
import * as readline from "node:readline/promises";
|
|
3
|
+
import { runInit } from "../lib/init.js";
|
|
4
|
+
import { normalizeHarnessFolderName, readHarnessRootConfig } from "../lib/harness-root.js";
|
|
5
|
+
import { writePackageHarnessRoot } from "../lib/package-json-config.js";
|
|
6
|
+
import { DEFAULT_HARNESS_ROOT } from "../lib/paths.js";
|
|
7
|
+
export async function init(args) {
|
|
8
|
+
const adopt = args.includes("--adopt");
|
|
9
|
+
const force = args.includes("--force");
|
|
10
|
+
const projectRoot = process.cwd();
|
|
11
|
+
const configuredRoot = await resolveInitHarnessRoot(projectRoot, args);
|
|
12
|
+
if (configuredRoot) {
|
|
13
|
+
await writePackageHarnessRoot(projectRoot, configuredRoot);
|
|
14
|
+
console.log(`configured package.json sdlcHarness.harnessFolderName=${JSON.stringify(configuredRoot)}`);
|
|
15
|
+
}
|
|
16
|
+
const report = await runInit(projectRoot, { adopt, force });
|
|
17
|
+
for (const line of report) {
|
|
18
|
+
console.log(line);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function resolveInitHarnessRoot(projectRoot, args) {
|
|
22
|
+
const argRoot = valueForArg(args, "--harness-folder") ?? valueForArg(args, "--harnessFolderName");
|
|
23
|
+
if (argRoot) {
|
|
24
|
+
return normalizeHarnessFolderName(argRoot);
|
|
25
|
+
}
|
|
26
|
+
const current = await readHarnessRootConfig(projectRoot);
|
|
27
|
+
if (current.source !== "default") {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return promptHarnessRoot(DEFAULT_HARNESS_ROOT);
|
|
31
|
+
}
|
|
32
|
+
async function promptHarnessRoot(defaultRoot) {
|
|
33
|
+
if (!input.isTTY || !output.isTTY) {
|
|
34
|
+
return defaultRoot;
|
|
35
|
+
}
|
|
36
|
+
const rl = readline.createInterface({ input, output });
|
|
37
|
+
try {
|
|
38
|
+
const answer = await rl.question(`Harness folder name (default ${defaultRoot}; press Enter to use default): `);
|
|
39
|
+
return normalizeHarnessFolderName(answer.trim() || defaultRoot);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
rl.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function valueForArg(args, name) {
|
|
46
|
+
const prefix = `${name}=`;
|
|
47
|
+
const inline = args.find((arg) => arg.startsWith(prefix));
|
|
48
|
+
if (inline) {
|
|
49
|
+
return inline.slice(prefix.length);
|
|
50
|
+
}
|
|
51
|
+
const index = args.indexOf(name);
|
|
52
|
+
if (index >= 0) {
|
|
53
|
+
return args[index + 1];
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function packageSource(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { checkSource, syncSource } from "../lib/package-source.js";
|
|
2
|
+
export async function packageSource(args) {
|
|
3
|
+
const subcommand = args[0] ?? "help";
|
|
4
|
+
if (subcommand === "sync-source") {
|
|
5
|
+
const report = await syncSource(process.cwd());
|
|
6
|
+
console.log(`package sync-source changed=${report.changed.length}`);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (subcommand === "check-source") {
|
|
10
|
+
const report = await checkSource(process.cwd());
|
|
11
|
+
if (report.drift.length > 0) {
|
|
12
|
+
for (const item of report.drift) {
|
|
13
|
+
console.error(`drift: ${item}`);
|
|
14
|
+
}
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.log("package source OK");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.log(`sdlc-harness package commands:
|
|
22
|
+
sync-source Update package canonical assets from this source workspace
|
|
23
|
+
check-source Verify package canonical assets match this source workspace`);
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sync(): Promise<void>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { runSync } from "../lib/sync-engine.js";
|
|
2
|
+
export async function sync() {
|
|
3
|
+
const report = await runSync(process.cwd());
|
|
4
|
+
console.log(`sync changed=${report.changed.length} skipped=${report.skipped.length} blocked=${report.blocked.length}`);
|
|
5
|
+
for (const blocked of report.blocked) {
|
|
6
|
+
console.error(`blocked: ${blocked}`);
|
|
7
|
+
}
|
|
8
|
+
if (report.blocked.length > 0) {
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function upgrade(): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function validate(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { runValidator } from "../lib/validators.js";
|
|
2
|
+
export async function validate(args) {
|
|
3
|
+
const gate = args[0] ?? "validate-harness";
|
|
4
|
+
const report = await runValidator(process.cwd(), gate);
|
|
5
|
+
for (const line of report.info) {
|
|
6
|
+
console.log(line);
|
|
7
|
+
}
|
|
8
|
+
for (const error of report.errors) {
|
|
9
|
+
console.error(`error: ${error}`);
|
|
10
|
+
}
|
|
11
|
+
if (report.errors.length > 0) {
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { commands } from "./commands/index.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HarnessConfig } from "./types.js";
|
|
2
|
+
export declare function defaultConfig(root: string): HarnessConfig;
|
|
3
|
+
export declare function readConfig(projectRoot: string): Promise<HarnessConfig>;
|
|
4
|
+
export declare function writeConfigIfMissing(projectRoot: string): Promise<boolean>;
|
|
5
|
+
export declare function normalizeConfig(value: Partial<HarnessConfig>, root?: string): HarnessConfig;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { harnessConfigPath, harnessPath, harnessRoot } from "./harness-root.js";
|
|
3
|
+
import { pathExists, readText, writeTextIfChanged } from "./fs.js";
|
|
4
|
+
import { parseYaml, stringifyYaml } from "./yaml.js";
|
|
5
|
+
export function defaultConfig(root) {
|
|
6
|
+
return {
|
|
7
|
+
core: {
|
|
8
|
+
package: "agent-project-sdlc",
|
|
9
|
+
version: "0.1.0",
|
|
10
|
+
schema_version: "1"
|
|
11
|
+
},
|
|
12
|
+
managed_files: [
|
|
13
|
+
{ path: "AGENTS.md", strategy: "merge-block" },
|
|
14
|
+
{ path: "Makefile", strategy: "merge-block" },
|
|
15
|
+
{ path: harnessPath(root, "skills"), strategy: "managed" },
|
|
16
|
+
{ path: harnessPath(root, "managed", "templates"), strategy: "managed" },
|
|
17
|
+
{ path: harnessPath(root, "managed", "policies"), strategy: "merge-with-local" },
|
|
18
|
+
{ path: harnessPath(root, "managed", "make", "sdlc-harness.mk"), strategy: "managed" },
|
|
19
|
+
{ path: ".github/workflows/harness.yml", strategy: "create-if-missing" }
|
|
20
|
+
],
|
|
21
|
+
local_overrides: [harnessPath(root, "overrides/**"), harnessPath(root, "managed", "policies", "*.local.yaml")],
|
|
22
|
+
never_overwrite: [".docs/**", harnessPath(root, "state/**"), "src/**", "tests/**"]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function readConfig(projectRoot) {
|
|
26
|
+
const root = await harnessRoot(projectRoot);
|
|
27
|
+
const configPath = path.join(projectRoot, await harnessConfigPath(projectRoot));
|
|
28
|
+
if (!(await pathExists(configPath))) {
|
|
29
|
+
return defaultConfig(root);
|
|
30
|
+
}
|
|
31
|
+
const parsed = parseYaml(await readText(configPath));
|
|
32
|
+
return normalizeConfig(parsed, root);
|
|
33
|
+
}
|
|
34
|
+
export async function writeConfigIfMissing(projectRoot) {
|
|
35
|
+
const root = await harnessRoot(projectRoot);
|
|
36
|
+
const configPath = path.join(projectRoot, await harnessConfigPath(projectRoot));
|
|
37
|
+
if (await pathExists(configPath)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
await writeTextIfChanged(configPath, stringifyYaml(defaultConfig(root)));
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
export function normalizeConfig(value, root = ".agent") {
|
|
44
|
+
const fallback = defaultConfig(root);
|
|
45
|
+
return {
|
|
46
|
+
core: {
|
|
47
|
+
package: value.core?.package ?? fallback.core.package,
|
|
48
|
+
version: value.core?.version ?? fallback.core.version,
|
|
49
|
+
schema_version: value.core?.schema_version ?? fallback.core.schema_version
|
|
50
|
+
},
|
|
51
|
+
managed_files: value.managed_files ?? fallback.managed_files,
|
|
52
|
+
local_overrides: value.local_overrides ?? fallback.local_overrides,
|
|
53
|
+
never_overwrite: value.never_overwrite ?? fallback.never_overwrite
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readConfig } from "./config.js";
|
|
3
|
+
import { harnessConfigPath, harnessPath, harnessRoot } from "./harness-root.js";
|
|
4
|
+
import { pathExists } from "./fs.js";
|
|
5
|
+
export async function runDoctor(projectRoot) {
|
|
6
|
+
const report = { info: [], warnings: [], errors: [] };
|
|
7
|
+
const root = await harnessRoot(projectRoot);
|
|
8
|
+
const relativeConfigPath = await harnessConfigPath(projectRoot);
|
|
9
|
+
const configPath = path.join(projectRoot, relativeConfigPath);
|
|
10
|
+
if (!(await pathExists(configPath))) {
|
|
11
|
+
report.errors.push(`missing ${relativeConfigPath}`);
|
|
12
|
+
return report;
|
|
13
|
+
}
|
|
14
|
+
const config = await readConfig(projectRoot);
|
|
15
|
+
report.info.push(`harness root: ${root}`);
|
|
16
|
+
report.info.push(`core package: ${config.core.package}@${config.core.version}`);
|
|
17
|
+
report.info.push(`schema version: ${config.core.schema_version}`);
|
|
18
|
+
for (const required of [harnessPath(root, "state", "lifecycle.yaml"), harnessPath(root, "state", "plan.yaml"), ".docs/INDEX.md"]) {
|
|
19
|
+
if (!(await pathExists(path.join(projectRoot, required)))) {
|
|
20
|
+
report.errors.push(`missing ${required}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const managed of config.managed_files) {
|
|
24
|
+
const exists = await pathExists(path.join(projectRoot, managed.path));
|
|
25
|
+
if (!exists && managed.strategy !== "create-if-missing") {
|
|
26
|
+
report.warnings.push(`managed path missing: ${managed.path}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
report.info.push("doctor complete");
|
|
30
|
+
return report;
|
|
31
|
+
}
|
package/dist/lib/fs.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function pathExists(target: string): Promise<boolean>;
|
|
2
|
+
export declare function readText(target: string): Promise<string>;
|
|
3
|
+
export declare function ensureDir(target: string): Promise<void>;
|
|
4
|
+
export declare function writeTextIfChanged(target: string, content: string): Promise<boolean>;
|
|
5
|
+
export declare function listFiles(root: string): Promise<string[]>;
|
|
6
|
+
export declare function copyTree(sourceRoot: string, destinationRoot: string, options?: {
|
|
7
|
+
skipGitkeep?: boolean;
|
|
8
|
+
}): Promise<string[]>;
|
package/dist/lib/fs.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function pathExists(target) {
|
|
4
|
+
try {
|
|
5
|
+
await fs.access(target);
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function readText(target) {
|
|
13
|
+
return fs.readFile(target, "utf8");
|
|
14
|
+
}
|
|
15
|
+
export async function ensureDir(target) {
|
|
16
|
+
await fs.mkdir(target, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
export async function writeTextIfChanged(target, content) {
|
|
19
|
+
if ((await pathExists(target)) && (await readText(target)) === content) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
await ensureDir(path.dirname(target));
|
|
23
|
+
await fs.writeFile(target, content, "utf8");
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
export async function listFiles(root) {
|
|
27
|
+
if (!(await pathExists(root))) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
31
|
+
const files = [];
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const fullPath = path.join(root, entry.name);
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
files.push(...(await listFiles(fullPath)));
|
|
36
|
+
}
|
|
37
|
+
else if (entry.isFile()) {
|
|
38
|
+
files.push(fullPath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
export async function copyTree(sourceRoot, destinationRoot, options = {}) {
|
|
44
|
+
const changed = [];
|
|
45
|
+
for (const source of await listFiles(sourceRoot)) {
|
|
46
|
+
if (options.skipGitkeep && path.basename(source) === ".gitkeep") {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const relative = path.relative(sourceRoot, source);
|
|
50
|
+
const destination = path.join(destinationRoot, relative);
|
|
51
|
+
if (await writeTextIfChanged(destination, await readText(source))) {
|
|
52
|
+
changed.push(destination);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return changed;
|
|
56
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface HarnessRootConfig {
|
|
2
|
+
harnessFolderName: string;
|
|
3
|
+
source: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function readHarnessRootConfig(projectRoot: string): Promise<HarnessRootConfig>;
|
|
6
|
+
export declare function harnessRoot(projectRoot: string): Promise<string>;
|
|
7
|
+
export declare function harnessConfigPath(projectRoot: string): Promise<string>;
|
|
8
|
+
export declare function harnessPath(root: string, ...segments: string[]): string;
|
|
9
|
+
export declare function normalizeHarnessFolderName(value: string): string;
|