phasegate 0.340.0 → 0.341.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/CHANGELOG.md +12 -0
- package/docs/folder_management_rules.md +10 -1
- package/docs/guide/cli-reference.md +23 -3
- package/docs/guide/configuration.md +28 -2
- package/docs/guide/hooks-integration.md +7 -1
- package/docs/guide/skills-overview.md +3 -0
- package/package.json +2 -1
- package/scripts/harness/agent-integration/application/usecases/handle-post-tool-use-usecase.ts +4 -0
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +36 -1
- package/scripts/harness/agent-integration/application/usecases/handle-stop-usecase.ts +3 -9
- package/scripts/harness/agent-integration/domain/ports/story-reflection-query-port.ts +2 -0
- package/scripts/harness/agent-integration/domain/services/hook-to-cli-translator.ts +8 -5
- package/scripts/harness/agent-integration/domain/value-objects/hook-translation-result.ts +1 -1
- package/scripts/harness/agent-integration/domain/value-objects/story-reflection-query-result.ts +10 -2
- package/scripts/harness/agent-integration/domain/value-objects/write-target-scope.ts +12 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/child-process-cli-executor-adapter.ts +64 -13
- package/scripts/harness/agent-integration/infrastructure/adapters/file-system-story-reflection-query-adapter.ts +108 -2
- package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +8 -6
- package/scripts/harness/agent-integration/presentation/hook-skip-event-recorder.ts +3 -0
- package/scripts/harness/agent-integration/presentation/post-tool-use-feedback.ts +39 -0
- package/scripts/harness/agent-integration/presentation/post-tool-use-hook.ts +20 -27
- package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +3 -0
- package/scripts/harness/agent-integration/presentation/stop-hook.ts +1 -3
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/biome-cli-executor-adapter.ts +21 -1
- package/scripts/harness/biome-ast-engine/infrastructure/adapters/typescript-source-module-analyzer-adapter.ts +5 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +15 -0
- package/scripts/harness/config-foundation/infrastructure/validators/ajv-config-schema-validator.ts +6 -3
- package/scripts/harness/harness-api/domain/ports/biome-lint-port.ts +2 -1
- package/scripts/harness/harness-api/domain/services/command-dispatch-service.ts +12 -1
- package/scripts/harness/harness-api/infrastructure/adapters/biome-ast-engine-lint-adapter.ts +10 -2
- package/scripts/harness/installation/application/bundled-skill-selection.ts +28 -2
- package/scripts/harness/installation/application/checks/check-utils.ts +2 -2
- package/scripts/harness/installation/application/usecases/run-install.ts +5 -5
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +62 -21
- package/scripts/harness/installation/presentation/cli/install-handler.ts +2 -1
- package/scripts/harness/main.ts +128 -90
- package/scripts/harness/phase-dependency-model/domain/services/story-reflection-checker.ts +48 -5
- package/scripts/harness/phase-dependency-model/domain/services/work-item-reflection-scope-resolver.ts +64 -0
- package/scripts/harness/phase-dependency-model/domain/values/story-reflection-mapping.ts +8 -2
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +6 -1
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-work-item-dependency-catalog.ts +73 -0
- package/scripts/harness/quick-mode/application/dto/change-category-classification-contract.ts +4 -0
- package/scripts/harness/quick-mode/application/ports/change-risk-advisory-port.ts +16 -0
- package/scripts/harness/quick-mode/composition-root.ts +5 -1
- package/scripts/harness/quick-mode/infrastructure/adapters/snapshot-risk-advisory-adapter.ts +118 -0
- package/scripts/harness/quick-mode/presentation/formatters/change-category-formatter.ts +7 -0
- package/scripts/harness/quick-mode/presentation/handlers/check-change-category-handler.ts +12 -1
- package/scripts/harness/setup/skill-deployer.ts +3 -45
- package/scripts/harness/skill-quality/application/dto/run-plan-checker-loop-output.ts +1 -0
- package/scripts/harness/skill-quality/application/usecases/apply-cascade-update-usecase.ts +9 -4
- package/scripts/harness/skill-quality/application/usecases/execute-tdd-cycle-usecase.ts +2 -1
- package/scripts/harness/skill-quality/application/usecases/run-plan-checker-loop-usecase.ts +8 -0
- package/scripts/harness/skill-quality/composition-root.ts +14 -7
- package/scripts/harness/skill-quality/domain/ports/plan-check-executor-port.ts +2 -0
- package/scripts/harness/skill-quality/domain/value-objects/cascade-update-target.ts +15 -1
- package/scripts/harness/skill-quality/infrastructure/adapters/l1-biome-validator-adapter.ts +9 -1
- package/scripts/harness/skill-quality/infrastructure/adapters/l2-validator-system-adapter.ts +18 -2
- package/scripts/harness/skill-quality/presentation/handlers/apply-cascade-update-handler.ts +8 -2
- package/scripts/harness/skill-quality/presentation/handlers/execute-tdd-cycle-handler.ts +4 -3
- package/scripts/harness/skill-quality/presentation/handlers/run-plan-checker-loop-handler.ts +11 -4
- package/scripts/harness/traceability-model/infrastructure/parsers/work-item-frontmatter-parser.ts +41 -0
- package/skills/README.md +7 -0
- package/skills/cascade-updater/SKILL.md +37 -109
- package/skills/cascade-updater/references//345/261/244/345/210/245/345/210/244/345/256/232/343/202/254/343/202/244/343/203/211.md +5 -3
- package/skills/release-publisher/SKILL.md +3 -1
- package/skills/skill-creator/SKILL.md +8 -9
|
@@ -86,7 +86,7 @@ export interface RunInstallInput {
|
|
|
86
86
|
readonly includeCodex?: boolean;
|
|
87
87
|
readonly includeHusky?: boolean;
|
|
88
88
|
readonly includeCi?: boolean;
|
|
89
|
-
readonly skillSet?:
|
|
89
|
+
readonly skillSet?: SkillSet;
|
|
90
90
|
readonly workflow?: "standard" | "strict";
|
|
91
91
|
readonly agent?: AgentTarget;
|
|
92
92
|
readonly personal?: boolean;
|
|
@@ -368,7 +368,7 @@ function renderAgentContextTemplate(
|
|
|
368
368
|
template: string,
|
|
369
369
|
options: {
|
|
370
370
|
readonly agent: AgentTarget;
|
|
371
|
-
readonly skillSet:
|
|
371
|
+
readonly skillSet: SkillSet;
|
|
372
372
|
readonly workflow: "standard" | "strict";
|
|
373
373
|
readonly includeHusky: boolean;
|
|
374
374
|
readonly includeCi: boolean;
|
|
@@ -388,7 +388,7 @@ function renderAgentContextTemplate(
|
|
|
388
388
|
.replaceAll("{{PHASEGATE_HUSKY_STATE}}", options.includeHusky ? "managed" : "not managed by this setup run")
|
|
389
389
|
.replaceAll("{{PHASEGATE_CI_STATE}}", options.includeCi ? "managed" : "not managed by this setup run")
|
|
390
390
|
.replaceAll("{{PHASEGATE_COMMANDS}}", commands)
|
|
391
|
-
.replaceAll("{{PHASEGATE_SKILLS}}", options.skillSet === "
|
|
391
|
+
.replaceAll("{{PHASEGATE_SKILLS}}", options.skillSet === "all" ? "- `all bundled skills`" : `- \`${options.skillSet} skills\``)
|
|
392
392
|
.replaceAll("{{PHASEGATE_PRESETS}}", "- `minimal`\n- `standard`\n- `full`\n- `custom`")
|
|
393
393
|
.replaceAll("{{PHASEGATE_USER_SECTION}}", USER_SECTION_PLACEHOLDER);
|
|
394
394
|
}
|
|
@@ -1146,13 +1146,13 @@ export class RunInstallUseCase {
|
|
|
1146
1146
|
private personalSkillsVersionHashInput(
|
|
1147
1147
|
path: string,
|
|
1148
1148
|
version: string,
|
|
1149
|
-
skillSet:
|
|
1149
|
+
skillSet: SkillSet,
|
|
1150
1150
|
skills: readonly string[],
|
|
1151
1151
|
): string {
|
|
1152
1152
|
return `personal-skills-version:${path}:${version}:${skillSet}:${skills.join(",")}`;
|
|
1153
1153
|
}
|
|
1154
1154
|
|
|
1155
|
-
private personalSkillHashInput(path: string, skill: string, version: string, skillSet:
|
|
1155
|
+
private personalSkillHashInput(path: string, skill: string, version: string, skillSet: SkillSet): string {
|
|
1156
1156
|
return `personal-skill:${path}:${skill}:${version}:${skillSet}`;
|
|
1157
1157
|
}
|
|
1158
1158
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// @work-item-id WI-331
|
|
13
13
|
// @work-item-id WI-385
|
|
14
14
|
// @work-item-id WI-387
|
|
15
|
+
// @work-item-id WI-220
|
|
15
16
|
|
|
16
17
|
import {
|
|
17
18
|
access,
|
|
@@ -31,7 +32,7 @@ import { DeploymentEntry } from "../../domain/deployment-entry.js";
|
|
|
31
32
|
import { DeploymentManifest } from "../../domain/deployment-manifest.js";
|
|
32
33
|
import type { ManagedBlockInput } from "../../domain/managed-block.js";
|
|
33
34
|
import type { RepairMode } from "../../domain/repair-mode.js";
|
|
34
|
-
import { getBundledSkillsForSet, type SkillSet } from "../bundled-skill-selection.js";
|
|
35
|
+
import { getBundledSkillsForSet, resolveInstalledSkillSet, type SkillSet } from "../bundled-skill-selection.js";
|
|
35
36
|
import { mergeNamedHookJson } from "../named-hook-json.js";
|
|
36
37
|
import type { HashCalculatorPort } from "../ports/hash-calculator-port.js";
|
|
37
38
|
import type { ManifestRepositoryPort } from "../ports/manifest-repository-port.js";
|
|
@@ -327,7 +328,7 @@ function reconcileManagedMarkdown(existing: string | null, incoming: string): st
|
|
|
327
328
|
return next.replace(/\s*$/, "\n");
|
|
328
329
|
}
|
|
329
330
|
|
|
330
|
-
function renderAgentContextTemplate(template: string): string {
|
|
331
|
+
function renderAgentContextTemplate(template: string, skillSet: SkillSet): string {
|
|
331
332
|
const commands = [
|
|
332
333
|
"phasegate doctor",
|
|
333
334
|
"phasegate phasegate:check-ready",
|
|
@@ -339,12 +340,12 @@ function renderAgentContextTemplate(template: string): string {
|
|
|
339
340
|
.join("\n");
|
|
340
341
|
return template
|
|
341
342
|
.replaceAll("{{PHASEGATE_AGENT}}", "both")
|
|
342
|
-
.replaceAll("{{PHASEGATE_SKILLS_MODE}}",
|
|
343
|
+
.replaceAll("{{PHASEGATE_SKILLS_MODE}}", skillSet)
|
|
343
344
|
.replaceAll("{{PHASEGATE_WORKFLOW}}", "standard")
|
|
344
345
|
.replaceAll("{{PHASEGATE_HUSKY_STATE}}", "managed")
|
|
345
346
|
.replaceAll("{{PHASEGATE_CI_STATE}}", "managed")
|
|
346
347
|
.replaceAll("{{PHASEGATE_COMMANDS}}", commands)
|
|
347
|
-
.replaceAll("{{PHASEGATE_SKILLS}}", "- `all bundled skills`")
|
|
348
|
+
.replaceAll("{{PHASEGATE_SKILLS}}", skillSet === "all" ? "- `all bundled skills`" : `- \`${skillSet} skills\``)
|
|
348
349
|
.replaceAll("{{PHASEGATE_PRESETS}}", "- `minimal`\n- `standard`\n- `full`\n- `custom`")
|
|
349
350
|
.replaceAll("{{PHASEGATE_USER_SECTION}}", USER_SECTION_PLACEHOLDER);
|
|
350
351
|
}
|
|
@@ -407,11 +408,13 @@ export class RunReconcileUseCase {
|
|
|
407
408
|
const changed: ReconcilePlanItem[] = [];
|
|
408
409
|
const backupStamp = `reconcile-${new Date().toISOString().replace(/[:.]/g, "-")}`;
|
|
409
410
|
let backupDir: string | null = null;
|
|
411
|
+
let repairedMetadata = false;
|
|
410
412
|
|
|
411
413
|
const outcomes: Array<{
|
|
412
414
|
readonly item: ReconcilePlanItem;
|
|
413
415
|
readonly needsBackup: boolean;
|
|
414
416
|
readonly prune?: boolean;
|
|
417
|
+
readonly recoveredContent?: string;
|
|
415
418
|
readonly apply: () => Promise<string | null>;
|
|
416
419
|
}> = [];
|
|
417
420
|
|
|
@@ -431,7 +434,7 @@ export class RunReconcileUseCase {
|
|
|
431
434
|
plan.push(item);
|
|
432
435
|
continue;
|
|
433
436
|
}
|
|
434
|
-
const outcome = await this.planManagedEntry(input, entry, target);
|
|
437
|
+
const outcome = await this.planManagedEntry(input, entry, target, manifest.version);
|
|
435
438
|
outcomes.push(outcome);
|
|
436
439
|
plan.push(outcome.item);
|
|
437
440
|
if (
|
|
@@ -460,16 +463,22 @@ export class RunReconcileUseCase {
|
|
|
460
463
|
}
|
|
461
464
|
|
|
462
465
|
const personalInstall = this.isPersonalManifest(manifest);
|
|
466
|
+
const skillSets = new Map<string, SkillSet>();
|
|
467
|
+
for (const root of personalInstall ? this.personalSkillPaths(manifest) : ["skills"]) {
|
|
468
|
+
skillSets.set(root, resolveInstalledSkillSet(await readTextOrNull(join(input.projectRoot, root, HARNESS_VERSION_BASENAME))));
|
|
469
|
+
}
|
|
463
470
|
if (!personalInstall && this.manifestIntendsSharedSkills(manifest)) {
|
|
464
|
-
const
|
|
465
|
-
const
|
|
471
|
+
const skillSet = skillSets.get("skills") ?? "all";
|
|
472
|
+
const sharedSkills = await listSelectedBundledSkills(input.harnessRoot, skillSet);
|
|
473
|
+
const outcome = await this.planSharedSkills(input, manifest, sharedSkills, skillSet);
|
|
466
474
|
outcomes.push(outcome);
|
|
467
475
|
plan.push(outcome.item);
|
|
468
476
|
}
|
|
469
477
|
if (personalInstall) {
|
|
470
|
-
const personalSkills = await listSelectedBundledSkills(input.harnessRoot, "all");
|
|
471
478
|
for (const skillPath of this.personalSkillPaths(manifest)) {
|
|
472
|
-
const
|
|
479
|
+
const skillSet = skillSets.get(skillPath) ?? "all";
|
|
480
|
+
const personalSkills = await listSelectedBundledSkills(input.harnessRoot, skillSet);
|
|
481
|
+
const outcome = await this.planPersonalSkills(input, manifest, skillPath, personalSkills, skillSet);
|
|
473
482
|
outcomes.push(outcome);
|
|
474
483
|
plan.push(outcome.item);
|
|
475
484
|
}
|
|
@@ -485,7 +494,17 @@ export class RunReconcileUseCase {
|
|
|
485
494
|
}
|
|
486
495
|
|
|
487
496
|
for (const outcome of outcomes) {
|
|
488
|
-
if (!outcome.item.changed)
|
|
497
|
+
if (!outcome.item.changed) {
|
|
498
|
+
const previous = nextManifest.findEntry(outcome.item.path);
|
|
499
|
+
if (previous !== null && outcome.recoveredContent !== undefined) {
|
|
500
|
+
nextManifest = nextManifest.addEntry(DeploymentEntry.create({
|
|
501
|
+
...previous,
|
|
502
|
+
hash: this.hashCalculator.compute(outcome.recoveredContent),
|
|
503
|
+
}));
|
|
504
|
+
repairedMetadata = true;
|
|
505
|
+
}
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
489
508
|
if (outcome.prune === true) {
|
|
490
509
|
await outcome.apply();
|
|
491
510
|
nextManifest = nextManifest.removeEntry(outcome.item.path);
|
|
@@ -500,16 +519,17 @@ export class RunReconcileUseCase {
|
|
|
500
519
|
changed.push(outcome.item);
|
|
501
520
|
if (hashContent !== null) {
|
|
502
521
|
if (outcome.item.strategy === "copy-dir" && outcome.item.path === "skills") {
|
|
503
|
-
const
|
|
522
|
+
const skillSet = skillSets.get("skills") ?? "all";
|
|
523
|
+
const sharedSkills = await listSelectedBundledSkills(input.harnessRoot, skillSet);
|
|
504
524
|
nextManifest = nextManifest.addEntry(
|
|
505
525
|
this.createdEntry(
|
|
506
526
|
SHARED_SKILLS_VERSION_PATH,
|
|
507
|
-
this.sharedSkillsVersionHashInput(input.phasegateVersion,
|
|
527
|
+
this.sharedSkillsVersionHashInput(input.phasegateVersion, skillSet, sharedSkills),
|
|
508
528
|
),
|
|
509
529
|
);
|
|
510
530
|
for (const skill of sharedSkills) {
|
|
511
531
|
nextManifest = nextManifest.addEntry(
|
|
512
|
-
this.createdEntry(`skills/${skill}`, this.sharedSkillHashInput(skill, input.phasegateVersion,
|
|
532
|
+
this.createdEntry(`skills/${skill}`, this.sharedSkillHashInput(skill, input.phasegateVersion, skillSet)),
|
|
513
533
|
);
|
|
514
534
|
}
|
|
515
535
|
} else if (
|
|
@@ -518,18 +538,19 @@ export class RunReconcileUseCase {
|
|
|
518
538
|
outcome.item.path === ".codex/skills" ||
|
|
519
539
|
outcome.item.path === ".agents/skills")
|
|
520
540
|
) {
|
|
521
|
-
const
|
|
541
|
+
const skillSet = skillSets.get(outcome.item.path) ?? "all";
|
|
542
|
+
const personalSkills = await listSelectedBundledSkills(input.harnessRoot, skillSet);
|
|
522
543
|
nextManifest = nextManifest.addEntry(
|
|
523
544
|
this.createdEntry(
|
|
524
545
|
`${outcome.item.path}/.harness-version`,
|
|
525
|
-
this.personalSkillsVersionHashInput(outcome.item.path, input.phasegateVersion,
|
|
546
|
+
this.personalSkillsVersionHashInput(outcome.item.path, input.phasegateVersion, skillSet, personalSkills),
|
|
526
547
|
),
|
|
527
548
|
);
|
|
528
549
|
for (const skill of personalSkills) {
|
|
529
550
|
nextManifest = nextManifest.addEntry(
|
|
530
551
|
this.createdEntry(
|
|
531
552
|
`${outcome.item.path}/${skill}`,
|
|
532
|
-
this.personalSkillHashInput(outcome.item.path, skill, input.phasegateVersion,
|
|
553
|
+
this.personalSkillHashInput(outcome.item.path, skill, input.phasegateVersion, skillSet),
|
|
533
554
|
),
|
|
534
555
|
);
|
|
535
556
|
}
|
|
@@ -553,13 +574,13 @@ export class RunReconcileUseCase {
|
|
|
553
574
|
}
|
|
554
575
|
}
|
|
555
576
|
|
|
556
|
-
if (changed.length > 0 || manifest.version !== input.phasegateVersion) {
|
|
577
|
+
if (changed.length > 0 || repairedMetadata || manifest.version !== input.phasegateVersion) {
|
|
557
578
|
await this.manifestRepository.save(input.projectRoot, nextManifest);
|
|
558
579
|
}
|
|
559
580
|
return { plan, refused, changed, backupDir };
|
|
560
581
|
}
|
|
561
582
|
|
|
562
|
-
private async planManagedEntry(input: RunReconcileInput, entry: DeploymentEntry, target: ReconcileTarget) {
|
|
583
|
+
private async planManagedEntry(input: RunReconcileInput, entry: DeploymentEntry, target: ReconcileTarget, previousVersion: string) {
|
|
563
584
|
if (target.strategy === "symlink") return this.planSymlink(input.projectRoot, target.path);
|
|
564
585
|
const absolutePath = this.resolveProjectPath(input.projectRoot, entry.path);
|
|
565
586
|
const before = await readTextOrNull(absolutePath);
|
|
@@ -567,7 +588,8 @@ export class RunReconcileUseCase {
|
|
|
567
588
|
const currentHash = this.hashCalculator.compute(before);
|
|
568
589
|
const matchesManifest = currentHash.equals(entry.hash);
|
|
569
590
|
const rawTemplate = target.templatePath ? await readFile(join(input.harnessRoot, target.templatePath), "utf8") : "";
|
|
570
|
-
const template = target.strategy === "markdown-managed"
|
|
591
|
+
const template = target.strategy === "markdown-managed"
|
|
592
|
+
? renderAgentContextTemplate(rawTemplate, await this.contextSkillSet(input.projectRoot, target.path)) : rawTemplate;
|
|
571
593
|
const next =
|
|
572
594
|
entry.mode === "created" &&
|
|
573
595
|
target.strategy !== "package-json" &&
|
|
@@ -576,6 +598,15 @@ export class RunReconcileUseCase {
|
|
|
576
598
|
? template
|
|
577
599
|
: this.reconcileContent(target, before, template, input.phasegateVersion);
|
|
578
600
|
const changed = before !== next;
|
|
601
|
+
// A no-op merge alone cannot attest user-edited content. Recover only a
|
|
602
|
+
// complete canonical output, or a package version-only change whose prior
|
|
603
|
+
// bytes can be reconstructed and matched against the recorded hash.
|
|
604
|
+
const recoveredContent = !changed && !matchesManifest && (
|
|
605
|
+
target.strategy === "package-json"
|
|
606
|
+
? this.hashCalculator.compute(this.reconcileContent(target, before, template, previousVersion)).equals(entry.hash)
|
|
607
|
+
: before === (entry.mode === "created" && target.strategy !== "markdown-managed" && target.strategy !== "json-named"
|
|
608
|
+
? template : this.reconcileContent(target, null, template, input.phasegateVersion))
|
|
609
|
+
) ? before : undefined;
|
|
579
610
|
const repairMode: RepairMode = matchesManifest ? "mechanical" : "ai-assisted";
|
|
580
611
|
return {
|
|
581
612
|
item: this.item(
|
|
@@ -584,11 +615,13 @@ export class RunReconcileUseCase {
|
|
|
584
615
|
repairMode,
|
|
585
616
|
target.strategy,
|
|
586
617
|
changed,
|
|
587
|
-
changed ? `${entry.path}: update managed portion` :
|
|
618
|
+
changed ? `${entry.path}: update managed portion` : recoveredContent !== undefined
|
|
619
|
+
? `${entry.path}: content already current; recover interrupted manifest hash` : `${entry.path}: already up to date`,
|
|
588
620
|
this.diffSummary(before, next),
|
|
589
621
|
repairMode === "ai-assisted" ? SKILL_HINT : null,
|
|
590
622
|
),
|
|
591
623
|
needsBackup: !matchesManifest || input.force,
|
|
624
|
+
recoveredContent,
|
|
592
625
|
apply: async () => {
|
|
593
626
|
await mkdir(dirname(absolutePath), { recursive: true });
|
|
594
627
|
await writeFile(absolutePath, next, "utf8");
|
|
@@ -598,12 +631,20 @@ export class RunReconcileUseCase {
|
|
|
598
631
|
};
|
|
599
632
|
}
|
|
600
633
|
|
|
634
|
+
private async contextSkillSet(projectRoot: string, contextPath: string): Promise<SkillSet> {
|
|
635
|
+
const agentRoot = contextPath.includes("CLAUDE") ? ".claude/skills" : ".codex/skills";
|
|
636
|
+
const metadata = await readTextOrNull(join(projectRoot, agentRoot, HARNESS_VERSION_BASENAME))
|
|
637
|
+
?? await readTextOrNull(join(projectRoot, SHARED_SKILLS_VERSION_PATH));
|
|
638
|
+
return resolveInstalledSkillSet(metadata);
|
|
639
|
+
}
|
|
640
|
+
|
|
601
641
|
private async planMissingTarget(input: RunReconcileInput, target: ReconcileTarget) {
|
|
602
642
|
if (target.strategy === "symlink") return this.planSymlink(input.projectRoot, target.path);
|
|
603
643
|
const absolutePath = this.resolveProjectPath(input.projectRoot, target.path);
|
|
604
644
|
const before = await readTextOrNull(absolutePath);
|
|
605
645
|
const rawTemplate = target.templatePath ? await readFile(join(input.harnessRoot, target.templatePath), "utf8") : "";
|
|
606
|
-
const template = target.strategy === "markdown-managed"
|
|
646
|
+
const template = target.strategy === "markdown-managed"
|
|
647
|
+
? renderAgentContextTemplate(rawTemplate, await this.contextSkillSet(input.projectRoot, target.path)) : rawTemplate;
|
|
607
648
|
const next =
|
|
608
649
|
before === null && target.strategy !== "package-json"
|
|
609
650
|
? template
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "../../application/operator-notice.js";
|
|
14
14
|
import type { RunInstallUseCase } from "../../application/usecases/run-install.js";
|
|
15
15
|
import type { AgentTarget } from "../../domain/agent-target.js";
|
|
16
|
+
import type { SkillSet } from "../../application/bundled-skill-selection.js";
|
|
16
17
|
|
|
17
18
|
export interface InstallHandlerInput {
|
|
18
19
|
readonly projectRoot: string;
|
|
@@ -26,7 +27,7 @@ export interface InstallHandlerInput {
|
|
|
26
27
|
readonly includeCodex?: boolean;
|
|
27
28
|
readonly includeHusky?: boolean;
|
|
28
29
|
readonly includeCi?: boolean;
|
|
29
|
-
readonly skillSet?:
|
|
30
|
+
readonly skillSet?: SkillSet;
|
|
30
31
|
readonly workflow?: "standard" | "strict";
|
|
31
32
|
readonly agent?: AgentTarget;
|
|
32
33
|
readonly personal?: boolean;
|