phasegate 0.160.5 → 0.160.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/CHANGELOG.md +6 -0
- package/docs/templates/ci/agent-context-refresh.yml +12 -8
- package/docs/templates/ci/consistency-check.yml +11 -7
- package/package.json +1 -1
- package/scripts/delegate-sonnet.sh +21 -7
- package/scripts/harness/ci-governance/application/usecases/refresh-claude-md-usecase.ts +6 -6
- package/scripts/harness/ci-governance/domain/services/claude-md-composer.ts +1 -1
- package/scripts/harness/ci-governance/infrastructure/adapters/yaml-template-renderer-adapter.ts +1 -0
- package/scripts/harness/ci-governance/presentation/formatters/ci-template-formatter.ts +3 -1
- package/scripts/harness/config-foundation/domain/harness-config.ts +1 -1
- package/scripts/harness/config-foundation/domain/value-objects/planning-mode-config.ts +2 -2
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +4 -2
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +4 -2
- package/scripts/harness/installation/application/checks/wi-workflow-drift-check.ts +2 -0
- package/scripts/harness/installation/application/usecases/run-install.ts +2 -1
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +9 -8
- package/scripts/harness/installation/application/usecases/run-uninstall.ts +13 -2
- package/scripts/harness/installation/presentation/cli/uninstall-handler.ts +4 -2
- package/scripts/harness/main.ts +67 -6
- package/scripts/harness/phase-dependency-model/application/services/phase-info-resolver.ts +1 -1
- package/scripts/harness/phase-dependency-model/domain/models/phase-structure.ts +1 -1
- package/scripts/harness/phase-dependency-model/domain/values/planning-mode.ts +2 -2
- package/scripts/harness/skill-quality/application/usecases/apply-cascade-update-usecase.ts +2 -1
- package/scripts/harness/skill-quality/presentation/handlers/apply-cascade-update-handler.ts +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.160.6] - 2026-05-14
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **WI-190..WI-196 — post-0.160.5 dogfood regressions** — aligns agent context refresh with reconcile rendering, adds retrofit planning-mode config plans, makes cascade update dry-runs non-mutating and explicit, fixes doctor `_shared` drift counts, removes pnpm-only scheduled CI templates, exposes `migrate work-items` help, and accepts positional `delegate-sonnet` prompts.
|
|
15
|
+
|
|
10
16
|
## [0.160.5] - 2026-05-14
|
|
11
17
|
|
|
12
18
|
### Fixed
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Phasegate — Agent context refresh workflow
|
|
2
|
+
# @work-item-id WI-194
|
|
2
3
|
#
|
|
3
4
|
# 使い方:
|
|
4
5
|
# このファイルを .github/workflows/agent-context-refresh.yml にコピーして使用する。
|
|
@@ -32,18 +33,21 @@ jobs:
|
|
|
32
33
|
uses: actions/setup-node@v4
|
|
33
34
|
with:
|
|
34
35
|
node-version: '20'
|
|
35
|
-
cache: 'pnpm'
|
|
36
|
-
|
|
37
|
-
- name: Install pnpm
|
|
38
|
-
uses: pnpm/action-setup@v4
|
|
39
|
-
with:
|
|
40
|
-
version: 9
|
|
41
36
|
|
|
42
37
|
- name: Install dependencies
|
|
43
|
-
run:
|
|
38
|
+
run: |
|
|
39
|
+
if [ -f pnpm-lock.yaml ]; then
|
|
40
|
+
pnpm install --frozen-lockfile
|
|
41
|
+
elif [ -f yarn.lock ]; then
|
|
42
|
+
yarn install --immutable || yarn install --frozen-lockfile
|
|
43
|
+
elif [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then
|
|
44
|
+
npm ci
|
|
45
|
+
else
|
|
46
|
+
npm install
|
|
47
|
+
fi
|
|
44
48
|
|
|
45
49
|
- name: Refresh agent context
|
|
46
|
-
run:
|
|
50
|
+
run: npx phasegate ci:auto-refresh-agent-context --apply
|
|
47
51
|
|
|
48
52
|
- name: Create pull request
|
|
49
53
|
uses: peter-evans/create-pull-request@v6
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Phasegate — 週次整合性チェックワークフロー
|
|
2
|
+
# @work-item-id WI-194
|
|
2
3
|
#
|
|
3
4
|
# 使い方:
|
|
4
5
|
# このファイルを .github/workflows/consistency-check.yml にコピーして使用する。
|
|
@@ -31,15 +32,18 @@ jobs:
|
|
|
31
32
|
uses: actions/setup-node@v4
|
|
32
33
|
with:
|
|
33
34
|
node-version: '20'
|
|
34
|
-
cache: 'pnpm'
|
|
35
|
-
|
|
36
|
-
- name: Install pnpm
|
|
37
|
-
uses: pnpm/action-setup@v4
|
|
38
|
-
with:
|
|
39
|
-
version: 9
|
|
40
35
|
|
|
41
36
|
- name: Install dependencies
|
|
42
|
-
run:
|
|
37
|
+
run: |
|
|
38
|
+
if [ -f pnpm-lock.yaml ]; then
|
|
39
|
+
pnpm install --frozen-lockfile
|
|
40
|
+
elif [ -f yarn.lock ]; then
|
|
41
|
+
yarn install --immutable || yarn install --frozen-lockfile
|
|
42
|
+
elif [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then
|
|
43
|
+
npm ci
|
|
44
|
+
else
|
|
45
|
+
npm install
|
|
46
|
+
fi
|
|
43
47
|
|
|
44
48
|
# L4-001: Drift Detection(設計⇔コード双方向乖離)
|
|
45
49
|
- name: L4 Drift Detection
|
package/package.json
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
# Usage:
|
|
5
5
|
# bash scripts/delegate-sonnet.sh --prompt "プロンプト" --output "出力パス"
|
|
6
6
|
# bash scripts/delegate-sonnet.sh --prompt-file /tmp/prompt.md --output "出力パス"
|
|
7
|
+
# bash scripts/delegate-sonnet.sh "プロンプト" --output "出力パス"
|
|
7
8
|
#
|
|
8
9
|
# Options:
|
|
9
10
|
# --prompt 委任プロンプト(直接指定、2000文字以下推奨)
|
|
10
11
|
# --prompt-file 委任プロンプトファイル(長文の場合)
|
|
11
|
-
# --output
|
|
12
|
+
# --output 出力ファイルパス(省略時: .phasegate/delegate-sonnet-output.md)
|
|
12
13
|
# --max-turns 最大ターン数(デフォルト: 30)
|
|
13
14
|
# --dry-run プロンプトを表示するだけで実行しない
|
|
14
15
|
|
|
@@ -20,6 +21,7 @@ PROMPT_FILE=""
|
|
|
20
21
|
OUTPUT_PATH=""
|
|
21
22
|
MAX_TURNS=30
|
|
22
23
|
DRY_RUN=false
|
|
24
|
+
POSITIONAL_ARGS=()
|
|
23
25
|
|
|
24
26
|
while [[ $# -gt 0 ]]; do
|
|
25
27
|
case $1 in
|
|
@@ -43,23 +45,35 @@ while [[ $# -gt 0 ]]; do
|
|
|
43
45
|
DRY_RUN=true
|
|
44
46
|
shift
|
|
45
47
|
;;
|
|
48
|
+
--)
|
|
49
|
+
shift
|
|
50
|
+
if [[ $# -gt 0 ]]; then
|
|
51
|
+
POSITIONAL_ARGS+=("$@")
|
|
52
|
+
fi
|
|
53
|
+
break
|
|
54
|
+
;;
|
|
46
55
|
*)
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
if [[ "$1" == --* ]]; then
|
|
57
|
+
echo "Unknown option: $1" >&2
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
POSITIONAL_ARGS+=("$1")
|
|
61
|
+
shift
|
|
49
62
|
;;
|
|
50
63
|
esac
|
|
51
64
|
done
|
|
52
65
|
|
|
66
|
+
if [[ -z "$PROMPT" && ${#POSITIONAL_ARGS[@]} -gt 0 ]]; then
|
|
67
|
+
PROMPT="${POSITIONAL_ARGS[*]}"
|
|
68
|
+
fi
|
|
69
|
+
|
|
53
70
|
# --- バリデーション ---
|
|
54
71
|
if [[ -z "$PROMPT" && -z "$PROMPT_FILE" ]]; then
|
|
55
72
|
echo "Error: --prompt or --prompt-file is required" >&2
|
|
56
73
|
exit 1
|
|
57
74
|
fi
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
echo "Error: --output is required" >&2
|
|
61
|
-
exit 1
|
|
62
|
-
fi
|
|
76
|
+
OUTPUT_PATH="${OUTPUT_PATH:-.phasegate/delegate-sonnet-output.md}"
|
|
63
77
|
|
|
64
78
|
if [[ -n "$PROMPT_FILE" && ! -f "$PROMPT_FILE" ]]; then
|
|
65
79
|
echo "Error: Prompt file not found: $PROMPT_FILE" >&2
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer application
|
|
3
3
|
* @unit ci-governance
|
|
4
|
+
* @work-item-id WI-190
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import type { AgentContextDocumentPort } from '../../domain/ports/agent-context-document-port.js';
|
|
@@ -12,12 +13,11 @@ const CLAUDE_MD_PATH = 'CLAUDE.md';
|
|
|
12
13
|
const CLAUDE_MD_TEMPLATE_PATH = 'docs/templates/agent-context/CLAUDE.md.template.md';
|
|
13
14
|
|
|
14
15
|
const PHASEGATE_COMMANDS = [
|
|
15
|
-
'phasegate
|
|
16
|
-
'phasegate ci:auto-refresh-agent-context --dry-run',
|
|
17
|
-
'phasegate ci:auto-refresh-agent-context --apply',
|
|
18
|
-
'phasegate refresh-claude-md --apply',
|
|
19
|
-
'phasegate p2:check-agent-context',
|
|
16
|
+
'phasegate doctor',
|
|
20
17
|
'phasegate phasegate:check-ready',
|
|
18
|
+
'phasegate validate --layer L2 --format human',
|
|
19
|
+
'phasegate setup:agent --dry-run',
|
|
20
|
+
'phasegate config:plan --intent l4-strict --dry-run',
|
|
21
21
|
];
|
|
22
22
|
|
|
23
23
|
const PHASE_PRESETS = ['minimal', 'standard', 'full', 'custom'];
|
|
@@ -37,7 +37,7 @@ export class RefreshClaudeMdUseCase {
|
|
|
37
37
|
]);
|
|
38
38
|
const nextContent = this.composer.compose(template, existing, {
|
|
39
39
|
commands: PHASEGATE_COMMANDS,
|
|
40
|
-
skills,
|
|
40
|
+
skills: ['all bundled skills'],
|
|
41
41
|
presets: PHASE_PRESETS,
|
|
42
42
|
});
|
|
43
43
|
const changed = existing !== nextContent;
|
|
@@ -11,7 +11,7 @@ export interface ClaudeMdTemplateValues {
|
|
|
11
11
|
|
|
12
12
|
const USER_SECTION_START = '<!-- phasegate:user-section:start -->';
|
|
13
13
|
const USER_SECTION_END = '<!-- phasegate:user-section:end -->';
|
|
14
|
-
const DEFAULT_USER_SECTION = '
|
|
14
|
+
const DEFAULT_USER_SECTION = 'Project-specific agent instructions go here.';
|
|
15
15
|
|
|
16
16
|
export class ClaudeMdComposer {
|
|
17
17
|
compose(template: string, existing: string | null, values: ClaudeMdTemplateValues): string {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer presentation
|
|
3
3
|
* @unit ci-governance
|
|
4
|
+
* @work-item-id WI-200
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import type { GenerateCiTemplateOutput } from '../../application/dto/generate-ci-template-output.js';
|
|
@@ -15,12 +16,13 @@ export class CiTemplateFormatter {
|
|
|
15
16
|
lines.push(` [${err.code}] ${err.message}`);
|
|
16
17
|
}
|
|
17
18
|
} else {
|
|
18
|
-
lines.push('✓ CI Template
|
|
19
|
+
lines.push('✓ CI Template Plan Ready');
|
|
19
20
|
lines.push(` Template Type: ${output.templateType}`);
|
|
20
21
|
lines.push(` Preset: ${output.presetRef}`);
|
|
21
22
|
lines.push(` Trigger: ${output.triggerCondition}`);
|
|
22
23
|
lines.push(` Validators: ${output.targetValidatorIds.join(', ')}`);
|
|
23
24
|
lines.push(` Fail on Warning: ${output.failOnWarning}`);
|
|
25
|
+
lines.push(' Output: no file written; use --render to print template YAML');
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
return lines.join('\n');
|
|
@@ -29,7 +29,7 @@ import type {
|
|
|
29
29
|
|
|
30
30
|
export type LayerId = 'L1' | 'L2' | 'L3' | 'L4';
|
|
31
31
|
export type PresetId = 'minimal' | 'standard' | 'strict';
|
|
32
|
-
export type PlanningModeValue = 'interactive' | 'embedded-qa';
|
|
32
|
+
export type PlanningModeValue = 'interactive' | 'embedded-qa' | 'manual';
|
|
33
33
|
export type { PhaseDependenciesPresetId };
|
|
34
34
|
type DeepPartial<T> = {
|
|
35
35
|
[K in keyof T]?: T[K] extends Array<infer TItem>
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* @unit config-foundation
|
|
4
4
|
*
|
|
5
5
|
* Planning Mode設定を表す値オブジェクト
|
|
6
|
-
* defaultMode と perPhase の値は列挙値 "interactive" | "embedded-qa" のみ許容
|
|
6
|
+
* defaultMode と perPhase の値は列挙値 "interactive" | "embedded-qa" | "manual" のみ許容
|
|
7
7
|
*/
|
|
8
8
|
import { ConfigValidationError } from '../errors/config-validation-error.js';
|
|
9
9
|
|
|
10
|
-
const VALID_MODES = ['interactive', 'embedded-qa'] as const;
|
|
10
|
+
const VALID_MODES = ['interactive', 'embedded-qa', 'manual'] as const;
|
|
11
11
|
type PlanningMode = (typeof VALID_MODES)[number];
|
|
12
12
|
|
|
13
13
|
export interface PlanningModeConfigProps {
|
package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json
CHANGED
|
@@ -384,7 +384,8 @@
|
|
|
384
384
|
"type": "string",
|
|
385
385
|
"enum": [
|
|
386
386
|
"interactive",
|
|
387
|
-
"embedded-qa"
|
|
387
|
+
"embedded-qa",
|
|
388
|
+
"manual"
|
|
388
389
|
]
|
|
389
390
|
},
|
|
390
391
|
"perPhase": {
|
|
@@ -393,7 +394,8 @@
|
|
|
393
394
|
"type": "string",
|
|
394
395
|
"enum": [
|
|
395
396
|
"interactive",
|
|
396
|
-
"embedded-qa"
|
|
397
|
+
"embedded-qa",
|
|
398
|
+
"manual"
|
|
397
399
|
]
|
|
398
400
|
}
|
|
399
401
|
}
|
package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json
CHANGED
|
@@ -394,7 +394,8 @@
|
|
|
394
394
|
"type": "string",
|
|
395
395
|
"enum": [
|
|
396
396
|
"interactive",
|
|
397
|
-
"embedded-qa"
|
|
397
|
+
"embedded-qa",
|
|
398
|
+
"manual"
|
|
398
399
|
]
|
|
399
400
|
},
|
|
400
401
|
"perPhase": {
|
|
@@ -403,7 +404,8 @@
|
|
|
403
404
|
"type": "string",
|
|
404
405
|
"enum": [
|
|
405
406
|
"interactive",
|
|
406
|
-
"embedded-qa"
|
|
407
|
+
"embedded-qa",
|
|
408
|
+
"manual"
|
|
407
409
|
]
|
|
408
410
|
}
|
|
409
411
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// @layer application
|
|
3
3
|
// @work-item-id WI-143
|
|
4
4
|
// @work-item-id WI-187
|
|
5
|
+
// @work-item-id WI-193
|
|
5
6
|
|
|
6
7
|
import { join, relative, sep } from "node:path";
|
|
7
8
|
import { DiagnosticFinding } from "../../domain/diagnostic-finding.js";
|
|
@@ -61,5 +62,6 @@ function isWorkItemDescription(path: string): boolean {
|
|
|
61
62
|
function isAdHocPlan(path: string): boolean {
|
|
62
63
|
if (!path.startsWith("docs/inception/")) return false;
|
|
63
64
|
if (/\/WI-\d{3}\//.test(path)) return false;
|
|
65
|
+
if (path.startsWith("docs/inception/_shared/") && path.endsWith(".md")) return true;
|
|
64
66
|
return path.includes("/codding_plan/") || path.endsWith("_plan.md");
|
|
65
67
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// @layer application
|
|
3
3
|
// @work-item-id WI-146
|
|
4
4
|
// @work-item-id WI-174
|
|
5
|
+
// @work-item-id WI-198
|
|
5
6
|
// @work-item-id WI-175
|
|
6
7
|
// @work-item-id WI-177
|
|
7
8
|
// @work-item-id WI-182
|
|
@@ -190,7 +191,7 @@ function renderAgentContextTemplate(
|
|
|
190
191
|
.replaceAll("{{PHASEGATE_HUSKY_STATE}}", options.includeHusky ? "managed" : "not managed by this setup run")
|
|
191
192
|
.replaceAll("{{PHASEGATE_CI_STATE}}", options.includeCi ? "managed" : "not managed by this setup run")
|
|
192
193
|
.replaceAll("{{PHASEGATE_COMMANDS}}", commands)
|
|
193
|
-
.replaceAll("{{PHASEGATE_SKILLS}}", options.skillSet === "core" ? "- core skills" : "- all bundled skills")
|
|
194
|
+
.replaceAll("{{PHASEGATE_SKILLS}}", options.skillSet === "core" ? "- `core skills`" : "- `all bundled skills`")
|
|
194
195
|
.replaceAll("{{PHASEGATE_PRESETS}}", "- `minimal`\n- `standard`\n- `full`\n- `custom`")
|
|
195
196
|
.replaceAll("{{PHASEGATE_USER_SECTION}}", "Project-specific agent instructions go here.");
|
|
196
197
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// @layer application
|
|
3
3
|
// @work-item-id WI-148
|
|
4
4
|
// @work-item-id WI-174
|
|
5
|
+
// @work-item-id WI-198
|
|
5
6
|
|
|
6
7
|
import { access, chmod, copyFile, lstat, mkdir, readFile, readlink, symlink, writeFile } from "node:fs/promises";
|
|
7
8
|
import { dirname, join, relative, resolve } from "node:path";
|
|
@@ -156,12 +157,12 @@ function reconcileManagedMarkdown(existing: string | null, incoming: string): st
|
|
|
156
157
|
|
|
157
158
|
function renderAgentContextTemplate(template: string): string {
|
|
158
159
|
const commands = [
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
].join("\n");
|
|
160
|
+
"phasegate doctor",
|
|
161
|
+
"phasegate phasegate:check-ready",
|
|
162
|
+
"phasegate validate --layer L2 --format human",
|
|
163
|
+
"phasegate setup:agent --dry-run",
|
|
164
|
+
"phasegate config:plan --intent l4-strict --dry-run",
|
|
165
|
+
].map((command) => `- \`${command}\``).join("\n");
|
|
165
166
|
return template
|
|
166
167
|
.replaceAll("{{PHASEGATE_AGENT}}", "both")
|
|
167
168
|
.replaceAll("{{PHASEGATE_SKILLS_MODE}}", "all")
|
|
@@ -169,7 +170,7 @@ function renderAgentContextTemplate(template: string): string {
|
|
|
169
170
|
.replaceAll("{{PHASEGATE_HUSKY_STATE}}", "managed")
|
|
170
171
|
.replaceAll("{{PHASEGATE_CI_STATE}}", "managed")
|
|
171
172
|
.replaceAll("{{PHASEGATE_COMMANDS}}", commands)
|
|
172
|
-
.replaceAll("{{PHASEGATE_SKILLS}}", "- all bundled skills")
|
|
173
|
+
.replaceAll("{{PHASEGATE_SKILLS}}", "- `all bundled skills`")
|
|
173
174
|
.replaceAll("{{PHASEGATE_PRESETS}}", "- `minimal`\n- `standard`\n- `full`\n- `custom`")
|
|
174
175
|
.replaceAll("{{PHASEGATE_USER_SECTION}}", "Project-specific agent instructions go here.");
|
|
175
176
|
}
|
|
@@ -299,7 +300,7 @@ export class RunReconcileUseCase {
|
|
|
299
300
|
const matchesManifest = currentHash.equals(entry.hash);
|
|
300
301
|
const rawTemplate = target.templatePath ? await readFile(join(input.harnessRoot, target.templatePath), "utf8") : "";
|
|
301
302
|
const template = target.strategy === "markdown-managed" ? renderAgentContextTemplate(rawTemplate) : rawTemplate;
|
|
302
|
-
const next = entry.mode === "created" && target.strategy !== "package-json"
|
|
303
|
+
const next = entry.mode === "created" && target.strategy !== "package-json" && target.strategy !== "markdown-managed"
|
|
303
304
|
? template
|
|
304
305
|
: this.reconcileContent(target, before, template, input.phasegateVersion);
|
|
305
306
|
const changed = before !== next;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// @layer application
|
|
3
3
|
// @work-item-id WI-147
|
|
4
4
|
// @work-item-id WI-174
|
|
5
|
+
// @work-item-id WI-199
|
|
5
6
|
|
|
6
7
|
import { access, copyFile, lstat, mkdir, readFile, readlink, rm, rmdir, writeFile } from "node:fs/promises";
|
|
7
8
|
import { dirname, join, relative, resolve } from "node:path";
|
|
@@ -19,6 +20,7 @@ export interface UninstallPlanItem {
|
|
|
19
20
|
readonly repairMode: RepairMode;
|
|
20
21
|
readonly strategy: StrategyType;
|
|
21
22
|
readonly changed: boolean;
|
|
23
|
+
readonly protected: boolean;
|
|
22
24
|
readonly summary: string;
|
|
23
25
|
readonly diff: string;
|
|
24
26
|
readonly skillHint: string | null;
|
|
@@ -46,6 +48,7 @@ const SHELL_END = "# === phasegate managed (END) ===";
|
|
|
46
48
|
const MARKDOWN_BEGIN = "<!-- phasegate:managed-section:start -->";
|
|
47
49
|
const MARKDOWN_END = "<!-- phasegate:managed-section:end -->";
|
|
48
50
|
const PHASEGATE_SCRIPT_PREFIX = "phasegate:";
|
|
51
|
+
const PROTECTED_UNINSTALL_PATHS = new Set(["package.json", "package-lock.json"]);
|
|
49
52
|
|
|
50
53
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
51
54
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -175,7 +178,7 @@ export class RunUninstallUseCase {
|
|
|
175
178
|
const outcome = await this.planEntry(input, entry);
|
|
176
179
|
outcomes.push({ entry, ...outcome });
|
|
177
180
|
plan.push(outcome.item);
|
|
178
|
-
if (input.apply && outcome.item.changed && (outcome.item
|
|
181
|
+
if (input.apply && outcome.item.changed && this.requiresForce(outcome.item) && !input.force) {
|
|
179
182
|
refused.push({ ...outcome.item, action: "refuse" });
|
|
180
183
|
}
|
|
181
184
|
}
|
|
@@ -372,6 +375,14 @@ export class RunUninstallUseCase {
|
|
|
372
375
|
diff: string,
|
|
373
376
|
skillHint: string | null,
|
|
374
377
|
): UninstallPlanItem {
|
|
375
|
-
return { path, action, repairMode, strategy, changed, summary, diff, skillHint };
|
|
378
|
+
return { path, action, repairMode, strategy, changed, protected: this.isProtectedPath(path), summary, diff, skillHint };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private requiresForce(item: UninstallPlanItem): boolean {
|
|
382
|
+
return item.protected || item.repairMode === "ai-assisted" || item.repairMode === "manual";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private isProtectedPath(path: string): boolean {
|
|
386
|
+
return PROTECTED_UNINSTALL_PATHS.has(path);
|
|
376
387
|
}
|
|
377
388
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @unit installation
|
|
2
2
|
// @layer presentation
|
|
3
3
|
// @work-item-id WI-147
|
|
4
|
+
// @work-item-id WI-199
|
|
4
5
|
|
|
5
6
|
import type { RunUninstallUseCase } from "../../application/usecases/run-uninstall.js";
|
|
6
7
|
|
|
@@ -33,14 +34,15 @@ export class UninstallHandler {
|
|
|
33
34
|
input.apply ? "phasegate uninstall apply" : "phasegate uninstall dry-run",
|
|
34
35
|
...result.plan.map((item) => {
|
|
35
36
|
const hint = item.skillHint ? `; hint: ${item.skillHint}` : "";
|
|
36
|
-
|
|
37
|
+
const protectedMarker = item.protected ? "; protected: true" : "";
|
|
38
|
+
return `- ${item.path}: ${item.action} (${item.repairMode}, ${item.strategy}${protectedMarker}); diff: ${item.diff}${hint}`;
|
|
37
39
|
}),
|
|
38
40
|
];
|
|
39
41
|
if (result.backupDir !== null) lines.push(`backups: ${result.backupDir}`);
|
|
40
42
|
if (result.archivedManifestPath !== null) lines.push(`archived manifest: ${result.archivedManifestPath}`);
|
|
41
43
|
if (result.refused.length > 0) {
|
|
42
44
|
lines.push("");
|
|
43
|
-
lines.push("Refused ai-assisted
|
|
45
|
+
lines.push("Refused protected, ai-assisted, or manual targets. Re-run with --force after reviewing the plan.");
|
|
44
46
|
}
|
|
45
47
|
return {
|
|
46
48
|
stdout: lines.join("\n"),
|
package/scripts/harness/main.ts
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
* @work-item-id WI-176
|
|
9
9
|
* @work-item-id WI-184
|
|
10
10
|
* @work-item-id WI-189
|
|
11
|
+
* @work-item-id WI-191
|
|
12
|
+
* @work-item-id WI-195
|
|
13
|
+
* @work-item-id WI-196
|
|
14
|
+
* @work-item-id WI-197
|
|
15
|
+
* @work-item-id WI-200
|
|
11
16
|
*
|
|
12
17
|
* Phasegate CLI エントリポイント。
|
|
13
18
|
* 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
|
|
@@ -171,6 +176,7 @@ Commands:
|
|
|
171
176
|
disable-feature <name> Disable a harness feature
|
|
172
177
|
list-features List available features
|
|
173
178
|
migrate Migrate phasegate.config.json (--schema v3, --config <path>)
|
|
179
|
+
migrate work-items Migrate legacy inception work item directories (--dry-run|--apply)
|
|
174
180
|
work-items:status Report or apply derived WI frontmatter status (--dry-run|--apply, --id, --fail-on-stale, --json)
|
|
175
181
|
|
|
176
182
|
render-errors Render harness errors (--format human|agent|ci)
|
|
@@ -211,7 +217,7 @@ Gate semantics:
|
|
|
211
217
|
refresh-claude-md Refresh CLAUDE.md standard sections (--dry-run, --apply, --json)
|
|
212
218
|
p2:check-agent-context Check AGENTS.md / CLAUDE.md freshness (--threshold-days <n>, --json)
|
|
213
219
|
setup:agent Plan agent-driven setup (--intent <minimal|recommended|strict|ci-only|agent-hooks|retrofit>, --agent <claude|codex|both>, --dry-run|--apply, --json)
|
|
214
|
-
config:plan Plan safe config changes (--intent <l4-strict|codex-hooks|ci-fail-on-warning|baseline-reset|quick-mode-strict>, --dry-run, --json)
|
|
220
|
+
config:plan Plan safe config changes (--intent <l4-strict|codex-hooks|ci-fail-on-warning|baseline-reset|quick-mode-strict|retrofit-bootstrap|planning-mode-relax>, --dry-run, --json)
|
|
215
221
|
ci:check-repetition Check error repetition (--code <errorCode>, --reset, --json)
|
|
216
222
|
baseline Create retrofit baseline snapshot (--dry-run, --force, --paths <glob,glob,...>, --json)
|
|
217
223
|
scaffold-design Scaffold a design doc (--unit <id>, --phase <logical|domain|uiux|unit-test|it-test>, --dry-run|--apply, --force, --json)
|
|
@@ -528,7 +534,7 @@ Options:
|
|
|
528
534
|
Produce an agent-readable configuration change plan.
|
|
529
535
|
|
|
530
536
|
Intents:
|
|
531
|
-
l4-strict, codex-hooks, ci-fail-on-warning, baseline-reset, quick-mode-strict
|
|
537
|
+
l4-strict, codex-hooks, ci-fail-on-warning, baseline-reset, quick-mode-strict, retrofit-bootstrap, planning-mode-relax
|
|
532
538
|
|
|
533
539
|
Options:
|
|
534
540
|
--dry-run
|
|
@@ -562,10 +568,11 @@ Options:
|
|
|
562
568
|
--help, -h Show this help`,
|
|
563
569
|
migrate: `Usage: phasegate migrate [options]
|
|
564
570
|
|
|
565
|
-
Migrate phasegate.config.json from older schema versions
|
|
571
|
+
Migrate phasegate.config.json from older schema versions, or migrate legacy inception work item directories.
|
|
566
572
|
|
|
567
573
|
Options:
|
|
568
574
|
--dry-run Preview changes without writing
|
|
575
|
+
--apply Apply migration when supported
|
|
569
576
|
--help, -h Show this help`,
|
|
570
577
|
"list-errors": `Usage: phasegate list-errors [options]
|
|
571
578
|
|
|
@@ -769,7 +776,7 @@ function parseCoverageThreshold(raw: string | undefined): number {
|
|
|
769
776
|
type InitPhasePreset = "full" | "standard" | "minimal" | "custom";
|
|
770
777
|
type AgentTarget = "claude" | "codex" | "both";
|
|
771
778
|
type SetupIntent = "minimal" | "recommended" | "strict" | "ci-only" | "agent-hooks" | "retrofit";
|
|
772
|
-
type ConfigChangeIntent = "l4-strict" | "codex-hooks" | "ci-fail-on-warning" | "baseline-reset" | "quick-mode-strict";
|
|
779
|
+
type ConfigChangeIntent = "l4-strict" | "codex-hooks" | "ci-fail-on-warning" | "baseline-reset" | "quick-mode-strict" | "retrofit-bootstrap" | "planning-mode-relax";
|
|
773
780
|
type SetupCompletenessStatus = "configured" | "planned" | "manual" | "not-applicable" | "unknown";
|
|
774
781
|
|
|
775
782
|
interface SetupCompletenessEntry {
|
|
@@ -847,7 +854,9 @@ function parseConfigChangeIntent(value: string | undefined): ConfigChangeIntent
|
|
|
847
854
|
value === "codex-hooks" ||
|
|
848
855
|
value === "ci-fail-on-warning" ||
|
|
849
856
|
value === "baseline-reset" ||
|
|
850
|
-
value === "quick-mode-strict"
|
|
857
|
+
value === "quick-mode-strict" ||
|
|
858
|
+
value === "retrofit-bootstrap" ||
|
|
859
|
+
value === "planning-mode-relax"
|
|
851
860
|
) {
|
|
852
861
|
return value;
|
|
853
862
|
}
|
|
@@ -1216,6 +1225,14 @@ function buildConfigPatchPreview(intent: ConfigChangeIntent, before: unknown | n
|
|
|
1216
1225
|
],
|
|
1217
1226
|
"codex-hooks": [],
|
|
1218
1227
|
"baseline-reset": [],
|
|
1228
|
+
"retrofit-bootstrap": [
|
|
1229
|
+
{ pointer: "/planningMode/default", path: ["planningMode", "default"], value: "manual" },
|
|
1230
|
+
{ pointer: "/phaseDependencies/override", path: ["phaseDependencies", "override"], value: true },
|
|
1231
|
+
{ pointer: "/quickMode/relaxedGates", path: ["quickMode", "relaxedGates"], value: ["phase-gate"] },
|
|
1232
|
+
],
|
|
1233
|
+
"planning-mode-relax": [
|
|
1234
|
+
{ pointer: "/planningMode/default", path: ["planningMode", "default"], value: "manual" },
|
|
1235
|
+
],
|
|
1219
1236
|
};
|
|
1220
1237
|
const changes = configIntents[intent];
|
|
1221
1238
|
if (changes.length === 0) {
|
|
@@ -1303,6 +1320,22 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1303
1320
|
validations: ["phasegate ci-check --quick --dry-run", "phasegate phasegate:check-ready"],
|
|
1304
1321
|
risks: ["More changes will require Full Mode validation before commit."],
|
|
1305
1322
|
},
|
|
1323
|
+
"retrofit-bootstrap": {
|
|
1324
|
+
targets: ["phasegate.config.json: planningMode.default", "phasegate.config.json: phaseDependencies.override", "phasegate.config.json: quickMode.relaxedGates"],
|
|
1325
|
+
managedTargets: ["phasegate.config.json"],
|
|
1326
|
+
externalActions: [],
|
|
1327
|
+
commands: ["phasegate baseline --dry-run", "phasegate config:plan --intent retrofit-bootstrap --json"],
|
|
1328
|
+
validations: ["phasegate validate-metadata docs/inception/_shared/*.md", "phasegate check-phase-gate --level 2"],
|
|
1329
|
+
risks: ["Manual planning mode accepts existing retrofit planning evidence; review the patch before applying it to avoid weakening greenfield projects."],
|
|
1330
|
+
},
|
|
1331
|
+
"planning-mode-relax": {
|
|
1332
|
+
targets: ["phasegate.config.json: planningMode.default"],
|
|
1333
|
+
managedTargets: ["phasegate.config.json"],
|
|
1334
|
+
externalActions: [],
|
|
1335
|
+
commands: ["phasegate config:plan --intent planning-mode-relax --json"],
|
|
1336
|
+
validations: ["phasegate check-phase-gate --level 2", "phasegate phasegate:check-ready"],
|
|
1337
|
+
risks: ["Manual planning mode reduces PhaseGate's QA enforcement for plan documents until strict planning is restored."],
|
|
1338
|
+
},
|
|
1306
1339
|
};
|
|
1307
1340
|
const before = await readProjectJson(rootDir, "phasegate.config.json");
|
|
1308
1341
|
return {
|
|
@@ -2371,6 +2404,16 @@ async function main(): Promise<void> {
|
|
|
2371
2404
|
break;
|
|
2372
2405
|
}
|
|
2373
2406
|
|
|
2407
|
+
case "status": {
|
|
2408
|
+
console.error("Warning: 'phasegate status' is deprecated; use 'phasegate phasegate:status'.");
|
|
2409
|
+
const mod = createHarnessApiModule();
|
|
2410
|
+
const flags: Record<string, boolean | string> = {};
|
|
2411
|
+
if (json) flags.json = true;
|
|
2412
|
+
await mod.handlers.status.handle({}, flags);
|
|
2413
|
+
if (!json) await printStoryReflectionStatusLine(rootDir);
|
|
2414
|
+
break;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2374
2417
|
case "phasegate:lint": {
|
|
2375
2418
|
const mod = createHarnessApiModule();
|
|
2376
2419
|
const flags: Record<string, boolean | string> = {};
|
|
@@ -2389,6 +2432,15 @@ async function main(): Promise<void> {
|
|
|
2389
2432
|
break;
|
|
2390
2433
|
}
|
|
2391
2434
|
|
|
2435
|
+
case "complete-check": {
|
|
2436
|
+
console.error("Warning: 'phasegate complete-check' is deprecated; use 'phasegate phasegate:complete-check'.");
|
|
2437
|
+
const mod = createHarnessApiModule();
|
|
2438
|
+
const flags: Record<string, boolean | string> = {};
|
|
2439
|
+
if (json) flags.json = true;
|
|
2440
|
+
await mod.handlers.completeCheck.handle({}, flags);
|
|
2441
|
+
break;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2392
2444
|
case "phasegate:impact-analysis": {
|
|
2393
2445
|
const mod = createHarnessApiModule();
|
|
2394
2446
|
const storyId = args[1] && !args[1].startsWith("--") ? args[1] : (parseFlag(args, "--story-id") ?? "");
|
|
@@ -2435,6 +2487,11 @@ Examples:
|
|
|
2435
2487
|
phasegate ci:generate-template --preset strict --type pre-commit --render`);
|
|
2436
2488
|
process.exit(0);
|
|
2437
2489
|
}
|
|
2490
|
+
const flagError = validateKnownFlags(args.slice(1), ["--preset", "--type", "--render", "--json", "--help"]);
|
|
2491
|
+
if (flagError !== null) {
|
|
2492
|
+
console.error(flagError);
|
|
2493
|
+
process.exit(2);
|
|
2494
|
+
}
|
|
2438
2495
|
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
2439
2496
|
const presetId = parseFlag(args, "--preset") ?? "standard";
|
|
2440
2497
|
const templateType = parseFlag(args, "--type") ?? "aidlc-gate";
|
|
@@ -2587,7 +2644,7 @@ Examples:
|
|
|
2587
2644
|
const mod = createSkillQualityHandlers();
|
|
2588
2645
|
const storyId = parseFlag(args, "--story") ?? "";
|
|
2589
2646
|
const dryRun = hasFlag(args, "--dry-run");
|
|
2590
|
-
const result = await mod.applyCascadeUpdateHandler.handle({ storyId, dryRun });
|
|
2647
|
+
const result = await mod.applyCascadeUpdateHandler.handle({ storyId, dryRun, format: json ? "json" : "human" });
|
|
2591
2648
|
console.log(result.message);
|
|
2592
2649
|
process.exit(result.exitCode);
|
|
2593
2650
|
break;
|
|
@@ -2780,6 +2837,10 @@ Examples:
|
|
|
2780
2837
|
}
|
|
2781
2838
|
|
|
2782
2839
|
case "delegate-sonnet": {
|
|
2840
|
+
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
|
|
2841
|
+
printSubcommandHelp("delegate-sonnet");
|
|
2842
|
+
process.exit(0);
|
|
2843
|
+
}
|
|
2783
2844
|
const { spawn } = await import("node:child_process");
|
|
2784
2845
|
const scriptPath = join(harnessRoot, "scripts/delegate-sonnet.sh");
|
|
2785
2846
|
const forwardArgs = args.slice(1);
|
|
@@ -125,7 +125,7 @@ export class PhaseInfoResolver {
|
|
|
125
125
|
blockers.push(`QAが未完了です: ${node.nodeKey()}`);
|
|
126
126
|
}
|
|
127
127
|
if (!actual.planningModeMatch) {
|
|
128
|
-
blockers.push(`Planning Mode
|
|
128
|
+
blockers.push(`Planning Mode evidence requirement is not satisfied: ${node.nodeKey()}`);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -270,7 +270,7 @@ export class PhaseStructure {
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
if (!planEvidence.planningModeMatch) {
|
|
273
|
-
blockers.push(`Planning Mode
|
|
273
|
+
blockers.push(`Planning Mode evidence requirement is not satisfied: ${node.nodeKey()}`);
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @unit phase-dependency-model
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export type PlanningModeValue = 'interactive' | 'embedded-qa';
|
|
6
|
+
export type PlanningModeValue = 'interactive' | 'embedded-qa' | 'manual';
|
|
7
7
|
|
|
8
8
|
export class InvalidPlanningModeError extends Error {
|
|
9
9
|
constructor(value: string) {
|
|
@@ -21,7 +21,7 @@ export class PlanningMode {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
static create(value: string): PlanningMode {
|
|
24
|
-
if (value !== 'interactive' && value !== 'embedded-qa') {
|
|
24
|
+
if (value !== 'interactive' && value !== 'embedded-qa' && value !== 'manual') {
|
|
25
25
|
throw new InvalidPlanningModeError(value);
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer application
|
|
3
3
|
* @unit skill-quality
|
|
4
|
+
* @work-item-id WI-192
|
|
4
5
|
*/
|
|
5
6
|
import { CascadeUpdateResult } from '../../domain/value-objects/cascade-update-result.js';
|
|
6
7
|
import type { CascadeUpdateService } from '../../domain/services/cascade-update-service.js';
|
|
@@ -35,7 +36,7 @@ export class ApplyCascadeUpdateUseCase {
|
|
|
35
36
|
const updatedContent = content.includes(target.storyIdTag)
|
|
36
37
|
? content
|
|
37
38
|
: `${content}\n${target.storyIdTag}`;
|
|
38
|
-
if (!input.dryRun) {
|
|
39
|
+
if (!input.dryRun && updatedContent !== content) {
|
|
39
40
|
await this.fileSystemPort.write(filePath, updatedContent);
|
|
40
41
|
}
|
|
41
42
|
updatedCount++;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer presentation
|
|
3
3
|
* @unit skill-quality
|
|
4
|
+
* @work-item-id WI-192
|
|
4
5
|
*/
|
|
5
6
|
import type { ApplyCascadeUpdateUseCase } from '../../application/usecases/apply-cascade-update-usecase.js';
|
|
6
7
|
|
|
7
8
|
export interface ApplyCascadeUpdateArgs {
|
|
8
9
|
storyId: string;
|
|
9
10
|
dryRun?: boolean;
|
|
11
|
+
format?: 'human' | 'json';
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export class ApplyCascadeUpdateHandler {
|
|
@@ -15,8 +17,15 @@ export class ApplyCascadeUpdateHandler {
|
|
|
15
17
|
async handle(args: ApplyCascadeUpdateArgs): Promise<{ exitCode: number; message: string }> {
|
|
16
18
|
try {
|
|
17
19
|
const output = await this.useCase.execute({ storyId: args.storyId, dryRun: args.dryRun });
|
|
20
|
+
if (args.format === 'json') {
|
|
21
|
+
return {
|
|
22
|
+
exitCode: output.errors.length > 0 ? 1 : 0,
|
|
23
|
+
message: JSON.stringify({ dryRun: args.dryRun === true, ...output }, null, 2),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
18
26
|
const tagsLine = output.appliedStoryIds.join(', ');
|
|
19
|
-
|
|
27
|
+
const verb = args.dryRun ? 'Would update' : 'Updated';
|
|
28
|
+
let msg = `${verb} ${output.updatedCount} files with tags: ${tagsLine}`;
|
|
20
29
|
|
|
21
30
|
if (output.errors.length > 0) {
|
|
22
31
|
const errLines = output.errors.map((e) => ` - ${e}`).join('\n');
|