phasegate 0.160.4 → 0.160.6
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 +12 -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/dto/scaffold-design-input.ts +2 -0
- package/scripts/harness/ci-governance/application/dto/scaffold-design-output.ts +2 -0
- package/scripts/harness/ci-governance/application/usecases/refresh-claude-md-usecase.ts +6 -6
- package/scripts/harness/ci-governance/application/usecases/scaffold-design-usecase.ts +17 -0
- 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/handlers/scaffold-design-handler.ts +25 -3
- 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/integrations/pre-commit.ts +7 -1
- package/scripts/harness/main.ts +88 -13
- 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,18 @@ 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
|
+
|
|
16
|
+
## [0.160.5] - 2026-05-14
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **WI-189 — umbrella CLI UX cleanup** — aligns `validate --format json` with the global JSON contract, makes `scaffold-design` default to dry-run with explicit `--apply`, fixes bypass audit empty-range wording, and synchronizes public help for scaffold, quick-mode, and delegate commands.
|
|
21
|
+
|
|
10
22
|
## [0.160.4] - 2026-05-14
|
|
11
23
|
|
|
12
24
|
### 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,11 +1,13 @@
|
|
|
1
1
|
// @unit ci-governance
|
|
2
2
|
// @layer application
|
|
3
|
+
// @work-item-id WI-189
|
|
3
4
|
|
|
4
5
|
export interface ScaffoldDesignOutput {
|
|
5
6
|
readonly targetPath: string;
|
|
6
7
|
readonly templatePath: string;
|
|
7
8
|
readonly unit: string;
|
|
8
9
|
readonly phase: string;
|
|
10
|
+
readonly dryRun: boolean;
|
|
9
11
|
readonly written: boolean;
|
|
10
12
|
readonly alreadyExists: boolean;
|
|
11
13
|
readonly overwritten: boolean;
|
|
@@ -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;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @unit ci-governance
|
|
2
2
|
// @layer application
|
|
3
|
+
// @work-item-id WI-189
|
|
3
4
|
|
|
4
5
|
import { DesignPhase } from '../../domain/value-objects/design-phase.js';
|
|
5
6
|
import type { TemplateRepositoryPort } from '../../domain/ports/template-repository-port.js';
|
|
@@ -21,18 +22,33 @@ export class ScaffoldDesignUseCase {
|
|
|
21
22
|
}
|
|
22
23
|
const phase = DesignPhase.create(input.phase);
|
|
23
24
|
const unit = input.unit.trim();
|
|
25
|
+
const dryRun = input.dryRun === true;
|
|
24
26
|
const force = input.force === true;
|
|
25
27
|
|
|
26
28
|
const templatePath = this.templates.resolvePath(phase);
|
|
27
29
|
const targetPath = this.writer.resolvePath(unit, phase);
|
|
28
30
|
const alreadyExists = await this.writer.exists(unit, phase);
|
|
29
31
|
|
|
32
|
+
if (dryRun) {
|
|
33
|
+
return {
|
|
34
|
+
targetPath,
|
|
35
|
+
templatePath,
|
|
36
|
+
unit,
|
|
37
|
+
phase: phase.value,
|
|
38
|
+
dryRun: true,
|
|
39
|
+
written: false,
|
|
40
|
+
alreadyExists,
|
|
41
|
+
overwritten: false,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
if (alreadyExists && !force) {
|
|
31
46
|
return {
|
|
32
47
|
targetPath,
|
|
33
48
|
templatePath,
|
|
34
49
|
unit,
|
|
35
50
|
phase: phase.value,
|
|
51
|
+
dryRun: false,
|
|
36
52
|
written: false,
|
|
37
53
|
alreadyExists: true,
|
|
38
54
|
overwritten: false,
|
|
@@ -48,6 +64,7 @@ export class ScaffoldDesignUseCase {
|
|
|
48
64
|
templatePath,
|
|
49
65
|
unit,
|
|
50
66
|
phase: phase.value,
|
|
67
|
+
dryRun: false,
|
|
51
68
|
written: true,
|
|
52
69
|
alreadyExists,
|
|
53
70
|
overwritten: alreadyExists && force,
|
|
@@ -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,5 +1,6 @@
|
|
|
1
1
|
// @unit ci-governance
|
|
2
2
|
// @layer presentation
|
|
3
|
+
// @work-item-id WI-189
|
|
3
4
|
|
|
4
5
|
import { DesignPhase } from '../../domain/value-objects/design-phase.js';
|
|
5
6
|
import type { ScaffoldDesignUseCase } from '../../application/usecases/scaffold-design-usecase.js';
|
|
@@ -7,6 +8,8 @@ import type { ScaffoldDesignUseCase } from '../../application/usecases/scaffold-
|
|
|
7
8
|
export interface ScaffoldDesignHandlerArgs {
|
|
8
9
|
readonly unit?: string;
|
|
9
10
|
readonly phase?: string;
|
|
11
|
+
readonly dryRun?: boolean;
|
|
12
|
+
readonly apply?: boolean;
|
|
10
13
|
readonly force?: boolean;
|
|
11
14
|
readonly format?: 'human' | 'json';
|
|
12
15
|
}
|
|
@@ -21,6 +24,12 @@ export class ScaffoldDesignHandler {
|
|
|
21
24
|
|
|
22
25
|
async handle(args: ScaffoldDesignHandlerArgs): Promise<ScaffoldDesignHandlerResult> {
|
|
23
26
|
const format = args.format ?? 'human';
|
|
27
|
+
const apply = args.apply === true;
|
|
28
|
+
const dryRun = args.dryRun === true || !apply;
|
|
29
|
+
|
|
30
|
+
if (args.dryRun === true && apply) {
|
|
31
|
+
return this.fail(format, '--dry-run と --apply は同時に指定できません', 2);
|
|
32
|
+
}
|
|
24
33
|
|
|
25
34
|
if (!args.unit || args.unit.trim().length === 0) {
|
|
26
35
|
return this.fail(format, '--unit <unit-id> は必須です', 2);
|
|
@@ -45,6 +54,7 @@ export class ScaffoldDesignHandler {
|
|
|
45
54
|
result = await this.useCase.execute({
|
|
46
55
|
unit: args.unit,
|
|
47
56
|
phase: args.phase,
|
|
57
|
+
dryRun,
|
|
48
58
|
force: args.force,
|
|
49
59
|
});
|
|
50
60
|
} catch (err) {
|
|
@@ -61,10 +71,22 @@ export class ScaffoldDesignHandler {
|
|
|
61
71
|
|
|
62
72
|
if (result.alreadyExists && !result.written) {
|
|
63
73
|
return {
|
|
64
|
-
exitCode: 2,
|
|
74
|
+
exitCode: result.dryRun ? 0 : 2,
|
|
75
|
+
output: [
|
|
76
|
+
result.dryRun ? `dry-run: 既に存在します: ${result.targetPath}` : `既に存在します: ${result.targetPath}`,
|
|
77
|
+
result.dryRun ? '書き込むには --apply を指定してください。' : '上書きするには --force を指定してください。',
|
|
78
|
+
].join('\n'),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (result.dryRun) {
|
|
83
|
+
return {
|
|
84
|
+
exitCode: 0,
|
|
65
85
|
output: [
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
`dry-run: 設計文書を生成予定: ${result.targetPath}`,
|
|
87
|
+
`テンプレ: ${result.templatePath}`,
|
|
88
|
+
`Unit: ${result.unit} / phase: ${result.phase}`,
|
|
89
|
+
'書き込むには --apply を指定してください。',
|
|
68
90
|
].join('\n'),
|
|
69
91
|
};
|
|
70
92
|
}
|
|
@@ -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
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @layer presentation
|
|
4
4
|
* @work-item-id WI-141
|
|
5
5
|
* @work-item-id WI-109
|
|
6
|
+
* @work-item-id WI-189
|
|
6
7
|
*
|
|
7
8
|
* Pre-commit CLI entry.
|
|
8
9
|
* Runs L2 validators against staged TypeScript files AND design-document
|
|
@@ -533,6 +534,11 @@ function hasCompleteBypassTrailerSet(results: readonly BypassTrailerValidationRe
|
|
|
533
534
|
return results.some((result) => result.hasAnyBypassTrailer && result.complete);
|
|
534
535
|
}
|
|
535
536
|
|
|
537
|
+
function toBypassAuditStdout(stdout: string, changedFiles: readonly string[]): string {
|
|
538
|
+
if (changedFiles.length > 0) return stdout;
|
|
539
|
+
return stdout.replace("No staged files to check. Skipping.", "No changed files in range to check. Skipping.");
|
|
540
|
+
}
|
|
541
|
+
|
|
536
542
|
export async function runBypassAudit(
|
|
537
543
|
deps: PreCommitDeps,
|
|
538
544
|
options: BypassAuditOptions = {},
|
|
@@ -554,7 +560,7 @@ export async function runBypassAudit(
|
|
|
554
560
|
|
|
555
561
|
const sections = [
|
|
556
562
|
`${BOLD}[phasegate]${RESET} Bypass audit (${baseRef}..${headRef})`,
|
|
557
|
-
result.stdout,
|
|
563
|
+
toBypassAuditStdout(result.stdout, changedFiles),
|
|
558
564
|
];
|
|
559
565
|
let exitCode = result.exitCode;
|
|
560
566
|
if (result.exitCode !== 0 && !hasCompleteBypassTrailerSet(bypassResults)) {
|
package/scripts/harness/main.ts
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* @work-item-id WI-175
|
|
8
8
|
* @work-item-id WI-176
|
|
9
9
|
* @work-item-id WI-184
|
|
10
|
+
* @work-item-id WI-189
|
|
11
|
+
* @work-item-id WI-191
|
|
12
|
+
* @work-item-id WI-195
|
|
13
|
+
* @work-item-id WI-196
|
|
10
14
|
*
|
|
11
15
|
* Phasegate CLI エントリポイント。
|
|
12
16
|
* 各Unitの Composition Root からハンドラーを取得し、コマンドに応じてディスパッチする。
|
|
@@ -156,7 +160,8 @@ Setup:
|
|
|
156
160
|
--with-husky, --with-ci, --yes)
|
|
157
161
|
update-skills Alias for reconcile (kept for compatibility)
|
|
158
162
|
doctor Diagnose silent installation failures (--json, --strict, --agent <claude|codex|both>, --report-out <path>)
|
|
159
|
-
scaffold-wi <unit> <
|
|
163
|
+
scaffold-wi <unit|_cross> <story|issue|chore>
|
|
164
|
+
Create docs/inception/{unit}/WI-XXX/description.md
|
|
160
165
|
emit-agent-rules Print AGENTS.md / CLAUDE.md WI workflow rules block
|
|
161
166
|
install Install phasegate managed files (--dry-run|--apply, --force)
|
|
162
167
|
uninstall Uninstall phasegate managed files (--dry-run|--apply, --force)
|
|
@@ -169,6 +174,7 @@ Commands:
|
|
|
169
174
|
disable-feature <name> Disable a harness feature
|
|
170
175
|
list-features List available features
|
|
171
176
|
migrate Migrate phasegate.config.json (--schema v3, --config <path>)
|
|
177
|
+
migrate work-items Migrate legacy inception work item directories (--dry-run|--apply)
|
|
172
178
|
work-items:status Report or apply derived WI frontmatter status (--dry-run|--apply, --id, --fail-on-stale, --json)
|
|
173
179
|
|
|
174
180
|
render-errors Render harness errors (--format human|agent|ci)
|
|
@@ -183,8 +189,9 @@ Commands:
|
|
|
183
189
|
|
|
184
190
|
lint Run lint checks (--json, --target <path>)
|
|
185
191
|
|
|
186
|
-
validate Run validators (--layer L2|L3|L4|all; L0 prints runtime hook info, --unit, --format human|agent|ci)
|
|
192
|
+
validate Run validators (--layer L2|L3|L4|all; L0 prints runtime hook info, --unit, --format human|agent|ci|json, --json)
|
|
187
193
|
ci-check CI check (--quick for quick mode, --fail-on-reject, --dry-run, --files)
|
|
194
|
+
check-change-category Classify changed paths for quick mode (--paths <csv>, --format human|json)
|
|
188
195
|
|
|
189
196
|
phasegate:check-ready Check ready status (--json)
|
|
190
197
|
phasegate:check-phase Check phase gate (--unit <unitId>, --json)
|
|
@@ -208,10 +215,10 @@ Gate semantics:
|
|
|
208
215
|
refresh-claude-md Refresh CLAUDE.md standard sections (--dry-run, --apply, --json)
|
|
209
216
|
p2:check-agent-context Check AGENTS.md / CLAUDE.md freshness (--threshold-days <n>, --json)
|
|
210
217
|
setup:agent Plan agent-driven setup (--intent <minimal|recommended|strict|ci-only|agent-hooks|retrofit>, --agent <claude|codex|both>, --dry-run|--apply, --json)
|
|
211
|
-
config:plan Plan safe config changes (--intent <l4-strict|codex-hooks|ci-fail-on-warning|baseline-reset|quick-mode-strict>, --dry-run, --json)
|
|
218
|
+
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)
|
|
212
219
|
ci:check-repetition Check error repetition (--code <errorCode>, --reset, --json)
|
|
213
220
|
baseline Create retrofit baseline snapshot (--dry-run, --force, --paths <glob,glob,...>, --json)
|
|
214
|
-
scaffold-design Scaffold a design doc (--unit <id>, --phase <logical|domain|uiux|unit-test|it-test>, --force, --json)
|
|
221
|
+
scaffold-design Scaffold a design doc (--unit <id>, --phase <logical|domain|uiux|unit-test|it-test>, --dry-run|--apply, --force, --json)
|
|
215
222
|
|
|
216
223
|
skill:execute-tdd-cycle Execute TDD cycle (--unit, --story, --desc, --phase RED|GREEN|REFACTOR, --passed)
|
|
217
224
|
skill:check-coverage Check coverage (--story <storyId>, --json)
|
|
@@ -409,7 +416,8 @@ function parseValidateFormat(args: readonly string[]): "human" | "agent" | "ci"
|
|
|
409
416
|
const raw = parseFlag(args, "--format");
|
|
410
417
|
if (raw === undefined) return undefined;
|
|
411
418
|
if (raw === "human" || raw === "agent" || raw === "ci") return raw;
|
|
412
|
-
|
|
419
|
+
if (raw === "json") return "ci";
|
|
420
|
+
throw new Error(`Invalid --format value for validate: '${raw}'. Supported values: human, agent, ci, json.`);
|
|
413
421
|
}
|
|
414
422
|
|
|
415
423
|
function levenshtein(a: string, b: string): number {
|
|
@@ -524,7 +532,7 @@ Options:
|
|
|
524
532
|
Produce an agent-readable configuration change plan.
|
|
525
533
|
|
|
526
534
|
Intents:
|
|
527
|
-
l4-strict, codex-hooks, ci-fail-on-warning, baseline-reset, quick-mode-strict
|
|
535
|
+
l4-strict, codex-hooks, ci-fail-on-warning, baseline-reset, quick-mode-strict, retrofit-bootstrap, planning-mode-relax
|
|
528
536
|
|
|
529
537
|
Options:
|
|
530
538
|
--dry-run
|
|
@@ -546,7 +554,8 @@ Run validators against the project. Without --layer, runs all enabled validator
|
|
|
546
554
|
|
|
547
555
|
Options:
|
|
548
556
|
--layer <L0|L2|L3|L4> Run only the specified validator layer; L0 prints runtime hook info
|
|
549
|
-
--json
|
|
557
|
+
--format <human|agent|ci|json> Output format; json is an alias for ci JSON
|
|
558
|
+
--json Output machine-readable JSON when --format is omitted
|
|
550
559
|
--help, -h Show this help`,
|
|
551
560
|
lint: `Usage: phasegate lint [options]
|
|
552
561
|
|
|
@@ -557,10 +566,11 @@ Options:
|
|
|
557
566
|
--help, -h Show this help`,
|
|
558
567
|
migrate: `Usage: phasegate migrate [options]
|
|
559
568
|
|
|
560
|
-
Migrate phasegate.config.json from older schema versions
|
|
569
|
+
Migrate phasegate.config.json from older schema versions, or migrate legacy inception work item directories.
|
|
561
570
|
|
|
562
571
|
Options:
|
|
563
572
|
--dry-run Preview changes without writing
|
|
573
|
+
--apply Apply migration when supported
|
|
564
574
|
--help, -h Show this help`,
|
|
565
575
|
"list-errors": `Usage: phasegate list-errors [options]
|
|
566
576
|
|
|
@@ -626,6 +636,28 @@ Options:
|
|
|
626
636
|
Examples:
|
|
627
637
|
phasegate check-change-category --paths src/foo.ts,src/bar.ts
|
|
628
638
|
phasegate check-change-category --paths src/foo.ts --format json`,
|
|
639
|
+
"scaffold-wi": `Usage: phasegate scaffold-wi <unit|_cross> <story|issue|chore>
|
|
640
|
+
|
|
641
|
+
Create docs/inception/{unit}/WI-XXX/description.md.
|
|
642
|
+
|
|
643
|
+
Arguments:
|
|
644
|
+
<unit|_cross> Unit id or _cross for cross-cutting work items.
|
|
645
|
+
<story|issue|chore> Work item type.
|
|
646
|
+
|
|
647
|
+
Options:
|
|
648
|
+
--help, -h Show this help`,
|
|
649
|
+
"scaffold-design": `Usage: phasegate scaffold-design --unit <id> --phase <phase> [options]
|
|
650
|
+
|
|
651
|
+
Scaffold a product construction design document.
|
|
652
|
+
|
|
653
|
+
Options:
|
|
654
|
+
--unit <id> Unit id under docs/product/construction.
|
|
655
|
+
--phase <phase> logical, domain, uiux, unit-test, or it-test.
|
|
656
|
+
--dry-run Preview target and template without writing (default).
|
|
657
|
+
--apply Write the scaffold.
|
|
658
|
+
--force Overwrite an existing target when applying.
|
|
659
|
+
--json Output machine-readable JSON.
|
|
660
|
+
--help, -h Show this help`,
|
|
629
661
|
"ci:generate-template": `Usage: phasegate ci:generate-template [options]
|
|
630
662
|
|
|
631
663
|
Generates a CI template configuration.
|
|
@@ -643,6 +675,15 @@ Options:
|
|
|
643
675
|
Examples:
|
|
644
676
|
phasegate ci:generate-template --type aidlc-gate
|
|
645
677
|
phasegate ci:generate-template --preset strict --type pre-commit --render`,
|
|
678
|
+
"delegate-sonnet": `Usage: phasegate delegate-sonnet [...args]
|
|
679
|
+
|
|
680
|
+
Delegate a task to Sonnet 4.6 by forwarding all args to scripts/delegate-sonnet.sh.
|
|
681
|
+
|
|
682
|
+
Arguments:
|
|
683
|
+
[...args] Task text and options consumed by the delegate script.
|
|
684
|
+
|
|
685
|
+
Options:
|
|
686
|
+
--help, -h Show this help`,
|
|
646
687
|
};
|
|
647
688
|
|
|
648
689
|
function printSubcommandHelp(command: string): void {
|
|
@@ -733,7 +774,7 @@ function parseCoverageThreshold(raw: string | undefined): number {
|
|
|
733
774
|
type InitPhasePreset = "full" | "standard" | "minimal" | "custom";
|
|
734
775
|
type AgentTarget = "claude" | "codex" | "both";
|
|
735
776
|
type SetupIntent = "minimal" | "recommended" | "strict" | "ci-only" | "agent-hooks" | "retrofit";
|
|
736
|
-
type ConfigChangeIntent = "l4-strict" | "codex-hooks" | "ci-fail-on-warning" | "baseline-reset" | "quick-mode-strict";
|
|
777
|
+
type ConfigChangeIntent = "l4-strict" | "codex-hooks" | "ci-fail-on-warning" | "baseline-reset" | "quick-mode-strict" | "retrofit-bootstrap" | "planning-mode-relax";
|
|
737
778
|
type SetupCompletenessStatus = "configured" | "planned" | "manual" | "not-applicable" | "unknown";
|
|
738
779
|
|
|
739
780
|
interface SetupCompletenessEntry {
|
|
@@ -811,7 +852,9 @@ function parseConfigChangeIntent(value: string | undefined): ConfigChangeIntent
|
|
|
811
852
|
value === "codex-hooks" ||
|
|
812
853
|
value === "ci-fail-on-warning" ||
|
|
813
854
|
value === "baseline-reset" ||
|
|
814
|
-
value === "quick-mode-strict"
|
|
855
|
+
value === "quick-mode-strict" ||
|
|
856
|
+
value === "retrofit-bootstrap" ||
|
|
857
|
+
value === "planning-mode-relax"
|
|
815
858
|
) {
|
|
816
859
|
return value;
|
|
817
860
|
}
|
|
@@ -1180,6 +1223,14 @@ function buildConfigPatchPreview(intent: ConfigChangeIntent, before: unknown | n
|
|
|
1180
1223
|
],
|
|
1181
1224
|
"codex-hooks": [],
|
|
1182
1225
|
"baseline-reset": [],
|
|
1226
|
+
"retrofit-bootstrap": [
|
|
1227
|
+
{ pointer: "/planningMode/default", path: ["planningMode", "default"], value: "manual" },
|
|
1228
|
+
{ pointer: "/phaseDependencies/override", path: ["phaseDependencies", "override"], value: true },
|
|
1229
|
+
{ pointer: "/quickMode/relaxedGates", path: ["quickMode", "relaxedGates"], value: ["phase-gate"] },
|
|
1230
|
+
],
|
|
1231
|
+
"planning-mode-relax": [
|
|
1232
|
+
{ pointer: "/planningMode/default", path: ["planningMode", "default"], value: "manual" },
|
|
1233
|
+
],
|
|
1183
1234
|
};
|
|
1184
1235
|
const changes = configIntents[intent];
|
|
1185
1236
|
if (changes.length === 0) {
|
|
@@ -1267,6 +1318,22 @@ async function buildConfigChangePlan(rootDir: string, intent: ConfigChangeIntent
|
|
|
1267
1318
|
validations: ["phasegate ci-check --quick --dry-run", "phasegate phasegate:check-ready"],
|
|
1268
1319
|
risks: ["More changes will require Full Mode validation before commit."],
|
|
1269
1320
|
},
|
|
1321
|
+
"retrofit-bootstrap": {
|
|
1322
|
+
targets: ["phasegate.config.json: planningMode.default", "phasegate.config.json: phaseDependencies.override", "phasegate.config.json: quickMode.relaxedGates"],
|
|
1323
|
+
managedTargets: ["phasegate.config.json"],
|
|
1324
|
+
externalActions: [],
|
|
1325
|
+
commands: ["phasegate baseline --dry-run", "phasegate config:plan --intent retrofit-bootstrap --json"],
|
|
1326
|
+
validations: ["phasegate validate-metadata docs/inception/_shared/*.md", "phasegate check-phase-gate --level 2"],
|
|
1327
|
+
risks: ["Manual planning mode accepts existing retrofit planning evidence; review the patch before applying it to avoid weakening greenfield projects."],
|
|
1328
|
+
},
|
|
1329
|
+
"planning-mode-relax": {
|
|
1330
|
+
targets: ["phasegate.config.json: planningMode.default"],
|
|
1331
|
+
managedTargets: ["phasegate.config.json"],
|
|
1332
|
+
externalActions: [],
|
|
1333
|
+
commands: ["phasegate config:plan --intent planning-mode-relax --json"],
|
|
1334
|
+
validations: ["phasegate check-phase-gate --level 2", "phasegate phasegate:check-ready"],
|
|
1335
|
+
risks: ["Manual planning mode reduces PhaseGate's QA enforcement for plan documents until strict planning is restored."],
|
|
1336
|
+
},
|
|
1270
1337
|
};
|
|
1271
1338
|
const before = await readProjectJson(rootDir, "phasegate.config.json");
|
|
1272
1339
|
return {
|
|
@@ -2191,7 +2258,7 @@ async function main(): Promise<void> {
|
|
|
2191
2258
|
const layer = parseFlag(args, "--layer") as "L0" | "L2" | "L3" | "L4" | "all" | undefined;
|
|
2192
2259
|
const unit = parseFlag(args, "--unit");
|
|
2193
2260
|
const phase = parseFlag(args, "--phase");
|
|
2194
|
-
const format = parseValidateFormat(args);
|
|
2261
|
+
const format = parseValidateFormat(args) ?? (json ? "ci" : undefined);
|
|
2195
2262
|
// WI-094 / ADR-017: --fail-on-warning / --no-fail-on-warning / 未指定→config値
|
|
2196
2263
|
const failOnWarning = parseTriStateFlag(args, "--fail-on-warning", "--no-fail-on-warning");
|
|
2197
2264
|
const noL4 = hasFlag(args, "--no-l4");
|
|
@@ -2206,7 +2273,7 @@ async function main(): Promise<void> {
|
|
|
2206
2273
|
targetPaths,
|
|
2207
2274
|
});
|
|
2208
2275
|
console.log(result.output);
|
|
2209
|
-
if (layer === "L2" || layer === "all") {
|
|
2276
|
+
if ((layer === "L2" || layer === "all") && format !== "ci") {
|
|
2210
2277
|
await printStoryReflectionValidationSummary(rootDir, unit);
|
|
2211
2278
|
}
|
|
2212
2279
|
process.exit(result.exitCode);
|
|
@@ -2492,11 +2559,15 @@ Examples:
|
|
|
2492
2559
|
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
2493
2560
|
const unit = parseFlag(args, "--unit") ?? "";
|
|
2494
2561
|
const phase = parseFlag(args, "--phase") ?? "";
|
|
2562
|
+
const dryRun = hasFlag(args, "--dry-run");
|
|
2563
|
+
const apply = hasFlag(args, "--apply");
|
|
2495
2564
|
const force = hasFlag(args, "--force");
|
|
2496
2565
|
const format = json ? "json" : "human";
|
|
2497
2566
|
const result = await mod.scaffoldDesignHandler.handle({
|
|
2498
2567
|
unit,
|
|
2499
2568
|
phase,
|
|
2569
|
+
dryRun,
|
|
2570
|
+
apply,
|
|
2500
2571
|
force,
|
|
2501
2572
|
format,
|
|
2502
2573
|
});
|
|
@@ -2547,7 +2618,7 @@ Examples:
|
|
|
2547
2618
|
const mod = createSkillQualityHandlers();
|
|
2548
2619
|
const storyId = parseFlag(args, "--story") ?? "";
|
|
2549
2620
|
const dryRun = hasFlag(args, "--dry-run");
|
|
2550
|
-
const result = await mod.applyCascadeUpdateHandler.handle({ storyId, dryRun });
|
|
2621
|
+
const result = await mod.applyCascadeUpdateHandler.handle({ storyId, dryRun, format: json ? "json" : "human" });
|
|
2551
2622
|
console.log(result.message);
|
|
2552
2623
|
process.exit(result.exitCode);
|
|
2553
2624
|
break;
|
|
@@ -2740,6 +2811,10 @@ Examples:
|
|
|
2740
2811
|
}
|
|
2741
2812
|
|
|
2742
2813
|
case "delegate-sonnet": {
|
|
2814
|
+
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
|
|
2815
|
+
printSubcommandHelp("delegate-sonnet");
|
|
2816
|
+
process.exit(0);
|
|
2817
|
+
}
|
|
2743
2818
|
const { spawn } = await import("node:child_process");
|
|
2744
2819
|
const scriptPath = join(harnessRoot, "scripts/delegate-sonnet.sh");
|
|
2745
2820
|
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');
|