agent-project-sdlc 0.1.5 → 0.1.7

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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # AI SDLC Harness
2
+
3
+ `agent-project-sdlc` provides the `sdlc-harness` CLI and canonical workflow assets for AI-assisted software delivery. It materializes an agent-readable lifecycle, workflow skills, templates, policies, gates and documentation structure into a project workspace.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install -D agent-project-sdlc
9
+ npx sdlc-harness init
10
+ ```
11
+
12
+ For existing projects:
13
+
14
+ ```sh
15
+ npx sdlc-harness init --adopt
16
+ ```
17
+
18
+ ## Capabilities
19
+
20
+ | Capability | Entry Point | Description |
21
+ |---|---|---|
22
+ | Project initialization | `npx sdlc-harness init` | Creates `AGENTS.md`, `<harnessRoot>/state/**`, workflow skills, managed templates/policies, `.docs/**` and a Makefile include. |
23
+ | Existing project adoption | `npx sdlc-harness init --adopt` | Adds Harness non-destructively to an existing repository. |
24
+ | Configurable Harness root | `--harness-folder`, `package.json#sdlcHarness.harnessFolderName`, `sdlc-harness.config.json` | Supports Codex `.codex`, Claude `.claude`, Cursor `.cursor`, Cline `.cline`, Roo `.roo`, Gemini `.gemini` or a custom folder. |
25
+ | Managed file sync | `npx sdlc-harness sync` | Materializes package canonical assets into the configured Harness root while preserving project state, docs and local overrides. |
26
+ | Upgrade | `npx sdlc-harness upgrade` | Runs migrations and sync for already-adopted projects. |
27
+ | Diagnostics | `npx sdlc-harness doctor` | Reports Harness root, package version, schema version and key managed paths. |
28
+ | Validators | `npx sdlc-harness validate-*`, `make validate-current`, `make validate-harness` | Checks phase deliverables, active plan shape, prompt language contract and generated overview freshness. |
29
+ | Lifecycle workflow | `<harnessRoot>/state/lifecycle.yaml`, `<harnessRoot>/state/plan.yaml`, `.docs/**` | Tracks REQUIREMENT_GATHERING, ARCHITECTING, SPRINTING, REVIEWING, TESTING, RELEASING and RFC_RECALIBRATION facts. |
30
+ | Natural-language control | `AGENTS.md` plus workflow skills | Lets users say things like "continue", "start development", "run tests" or "requirements changed"; agents map these to workflow actions. |
31
+ | Workflow skills | `<harnessRoot>/skills/pjsdlc_*/SKILL.md` | Provides role prompts for product, architecture, development, implementation docs, review, testing, release and RFC recalibration. |
32
+ | Project-local skill overrides | `<harnessRoot>/pjsdlc_managed/override_skills/<skill_name>.md` + `npx sdlc-harness sync` | Appends project-specific role instructions to generated Skill output without editing managed Skill files. |
33
+ | Local policy overrides | `<harnessRoot>/pjsdlc_managed/policies/*.local.yaml` | Preserves project-specific policy additions separately from package defaults. |
34
+ | Documentation overview | `make docs-overview`, `make validate-doc-overviews` | Regenerates human-readable stage overviews from `.docs/**` fact slices. |
35
+ | Package source checks | `sdlc-harness package sync-source`, `sdlc-harness package check-source` | Maintainer commands for keeping package canonical assets aligned with this source workspace. |
36
+
37
+ ## Skill Overrides
38
+
39
+ Do not edit generated files under `<harnessRoot>/skills/**/SKILL.md`; `sync` and `upgrade` regenerate them.
40
+
41
+ To customize a stage role prompt, create:
42
+
43
+ ```txt
44
+ <harnessRoot>/pjsdlc_managed/override_skills/<skill_name>.md
45
+ ```
46
+
47
+ Example:
48
+
49
+ ```txt
50
+ .codex/pjsdlc_managed/override_skills/pjsdlc_dev_sprint.md
51
+ ```
52
+
53
+ Then run:
54
+
55
+ ```sh
56
+ npx sdlc-harness sync
57
+ ```
58
+
59
+ The sync output is the package base Skill plus one appended `Local Override` block. Unknown skill names block sync so misspellings do not silently fail.
60
+
61
+ ## Common Commands
62
+
63
+ ```sh
64
+ npx sdlc-harness init
65
+ npx sdlc-harness init --adopt
66
+ npx sdlc-harness sync
67
+ npx sdlc-harness upgrade
68
+ npx sdlc-harness doctor
69
+ make validate-current
70
+ make validate-harness
71
+ make docs-overview
72
+ ```
73
+
74
+ ## More Information
75
+
76
+ The source repository keeps the full product and architecture specification in `PROJECT_SPEC.md`, with implementation and release evidence under `.docs/**`.
@@ -15,6 +15,8 @@ description: Use when ...
15
15
 
16
16
  角色提示词应保持通用,不绑定具体业务项目;如果必须依赖项目事实,应要求读取 `.docs/`、`<harnessRoot>/state/**` 或当前 task contract,而不是写入固定业务假设。
17
17
 
18
+ 项目如果需要补充阶段角色要求,应在 `<harnessRoot>/pjsdlc_managed/override_skills/<skill_name>.md` 写追加提示词,并运行 `sdlc-harness sync` 合成最终 `SKILL.md`;不要直接修改 package-managed Skill 文件。
19
+
18
20
  ## 输入
19
21
 
20
22
  - `<harnessRoot>/state/lifecycle.yaml`
@@ -21,7 +21,10 @@ export function defaultConfig(root) {
21
21
  { path: harnessPath(root, "pjsdlc_managed", "make", "sdlc-harness.mk"), strategy: "managed" },
22
22
  { path: ".github/workflows/harness.yml", strategy: "create-if-missing" }
23
23
  ],
24
- local_overrides: [harnessPath(root, "overrides/**"), harnessPath(root, "pjsdlc_managed", "policies", "*.local.yaml")],
24
+ local_overrides: [
25
+ harnessPath(root, "pjsdlc_managed", "override_skills", "*.md"),
26
+ harnessPath(root, "pjsdlc_managed", "policies", "*.local.yaml")
27
+ ],
25
28
  never_overwrite: [".docs/**", harnessPath(root, "state/**"), "src/**", "tests/**"]
26
29
  };
27
30
  }
@@ -56,6 +56,7 @@ async function migrateLegacyManagedPaths(projectRoot, root, report) {
56
56
  await moveIfDestinationMissing(projectRoot, harnessPath(root, "templates"), harnessPath(root, "pjsdlc_managed", "templates"), report);
57
57
  await moveIfDestinationMissing(projectRoot, harnessPath(root, "policies"), harnessPath(root, "pjsdlc_managed", "policies"), report);
58
58
  await moveIfDestinationMissing(projectRoot, harnessPath(root, "make"), harnessPath(root, "pjsdlc_managed", "make"), report);
59
+ await moveIfDestinationMissing(projectRoot, harnessPath(root, "overrides", "skills"), harnessPath(root, "pjsdlc_managed", "override_skills"), report);
59
60
  }
60
61
  async function movePromptTreeToSkillsIfDestinationMissing(projectRoot, legacyRelativePath, nextRelativePath, report) {
61
62
  const legacyPath = path.join(projectRoot, legacyRelativePath);
@@ -104,6 +105,11 @@ async function moveIfDestinationMissing(projectRoot, legacyRelativePath, nextRel
104
105
  report.changed.push(`${legacyRelativePath} -> ${nextRelativePath}`);
105
106
  }
106
107
  function migrateLocalOverride(item, root) {
108
+ if (item === harnessPath(root, "overrides/**") ||
109
+ item === harnessPath(root, "overrides", "skills", "*.md") ||
110
+ item === harnessPath(root, "overrides", "skills", "**")) {
111
+ return harnessPath(root, "pjsdlc_managed", "override_skills", "*.md");
112
+ }
107
113
  if (item === ".harness/policies/*.local.yaml" ||
108
114
  item === harnessPath(root, "managed", "policies", "*.local.yaml")) {
109
115
  return harnessPath(root, "pjsdlc_managed", "policies", "*.local.yaml");
@@ -30,14 +30,8 @@ async function syncManagedFile(projectRoot, root, managedFile, report) {
30
30
  await syncMakefileInclude(destination, root, report);
31
31
  return;
32
32
  }
33
- if (managedFile.path === path.join(root, "skills")) {
34
- await syncTree(packageAssetPath("skills"), destination, report);
35
- return;
36
- }
37
- if (managedFile.path === path.join(root, "skills") ||
38
- managedFile.path === ".harness/agents/skills" ||
39
- (managedFile.path === ".agents/skills" && root !== ".agents")) {
40
- await syncTree(packageAssetPath("skills"), path.join(projectRoot, root, "skills"), report);
33
+ if (isSkillsManagedPath(managedFile.path, root)) {
34
+ await syncSkillsTree(packageAssetPath("skills"), path.join(projectRoot, root, "skills"), projectRoot, root, report);
41
35
  return;
42
36
  }
43
37
  if (managedFile.path === path.join(root, "pjsdlc_managed", "templates")) {
@@ -62,6 +56,11 @@ async function syncManagedFile(projectRoot, root, managedFile, report) {
62
56
  }
63
57
  report.skipped.push(managedFile.path);
64
58
  }
59
+ function isSkillsManagedPath(managedPath, root) {
60
+ return (managedPath === path.join(root, "skills") ||
61
+ managedPath === ".harness/agents/skills" ||
62
+ (managedPath === ".agents/skills" && root !== ".agents"));
63
+ }
65
64
  async function syncAgentsBlock(destination, root, report) {
66
65
  const corePath = packageAssetPath("agents", "AGENTS_CORE.md");
67
66
  if (!(await pathExists(corePath))) {
@@ -197,6 +196,93 @@ async function syncTree(source, destination, report) {
197
196
  const changed = await copyTree(source, destination, { skipGitkeep: true });
198
197
  report.changed.push(...changed);
199
198
  }
199
+ async function syncSkillsTree(source, destination, projectRoot, root, report) {
200
+ if (!(await pathExists(source))) {
201
+ report.skipped.push(path.basename(destination));
202
+ return;
203
+ }
204
+ const files = await listFiles(source);
205
+ const realFiles = files.filter((file) => !file.endsWith(".gitkeep"));
206
+ if (realFiles.length === 0) {
207
+ report.skipped.push(path.basename(destination));
208
+ return;
209
+ }
210
+ const knownSkills = new Set();
211
+ for (const file of realFiles) {
212
+ const skillName = skillNameForSourceFile(source, file);
213
+ if (skillName) {
214
+ knownSkills.add(skillName);
215
+ }
216
+ }
217
+ const blockedBefore = report.blocked.length;
218
+ const overrides = await readSkillOverrides(projectRoot, root, knownSkills, report);
219
+ if (report.blocked.length > blockedBefore) {
220
+ return;
221
+ }
222
+ for (const file of realFiles) {
223
+ const relative = path.relative(source, file);
224
+ const destinationFile = path.join(destination, relative);
225
+ const skillName = skillNameForSourceFile(source, file);
226
+ const baseContent = await readText(file);
227
+ const content = skillName ? renderSkillWithOverride(baseContent, overrides.get(skillName)) : baseContent;
228
+ if (await writeTextIfChanged(destinationFile, content)) {
229
+ report.changed.push(destinationFile);
230
+ }
231
+ else {
232
+ report.skipped.push(destinationFile);
233
+ }
234
+ }
235
+ }
236
+ async function readSkillOverrides(projectRoot, root, knownSkills, report) {
237
+ const overrides = new Map();
238
+ const overrideRoot = skillOverrideRoot(projectRoot, root);
239
+ if (!(await pathExists(overrideRoot))) {
240
+ return overrides;
241
+ }
242
+ for (const file of await listFiles(overrideRoot)) {
243
+ if (path.basename(file) === ".gitkeep") {
244
+ continue;
245
+ }
246
+ const relativePath = path.relative(overrideRoot, file).split(path.sep).join("/");
247
+ const match = relativePath.match(/^([^/]+)\.md$/);
248
+ if (!match || !knownSkills.has(match[1])) {
249
+ report.blocked.push(`unknown skill override: ${path.join(root, "pjsdlc_managed", "override_skills", relativePath)}`);
250
+ continue;
251
+ }
252
+ const content = await readText(file);
253
+ if (content.trim()) {
254
+ overrides.set(match[1], {
255
+ relativePath: path.join(root, "pjsdlc_managed", "override_skills", relativePath),
256
+ content
257
+ });
258
+ }
259
+ }
260
+ return overrides;
261
+ }
262
+ function skillOverrideRoot(projectRoot, root) {
263
+ return path.join(projectRoot, root, "pjsdlc_managed", "override_skills");
264
+ }
265
+ function skillNameForSourceFile(sourceRoot, file) {
266
+ const relative = path.relative(sourceRoot, file).split(path.sep).join("/");
267
+ const match = relative.match(/^([^/]+)\/SKILL\.md$/);
268
+ return match?.[1];
269
+ }
270
+ function renderSkillWithOverride(baseContent, override) {
271
+ if (!override) {
272
+ return baseContent;
273
+ }
274
+ const header = [
275
+ "",
276
+ "",
277
+ "## Local Override",
278
+ "",
279
+ `Source: \`${override.relativePath.split(path.sep).join("/")}\``,
280
+ "",
281
+ "The following project-local instructions are appended by `sdlc-harness sync`. Keep package-managed Skill files unchanged; edit the override source instead.",
282
+ ""
283
+ ].join("\n");
284
+ return `${baseContent.trimEnd()}${header}${override.content.trim()}\n`;
285
+ }
200
286
  async function syncFile(source, destination, report, missingMode) {
201
287
  if (!(await pathExists(source))) {
202
288
  if (missingMode === "block-if-missing") {
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "agent-project-sdlc",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "CLI and canonical assets for the AI SDLC Harness workflow.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "sdlc-harness": "dist/cli.js"
8
8
  },
9
9
  "files": [
10
+ "README.md",
10
11
  "dist",
11
12
  "assets",
12
13
  "migrations",