project-tiny-context-harness 0.2.54 → 0.2.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -65
- package/assets/README.md +81 -58
- package/assets/README.zh-CN.md +8 -6
- package/assets/agents/AGENTS_CORE.md +17 -16
- package/assets/context_templates/product-surface-contract.md +60 -0
- package/assets/github/harness.yml +4 -4
- package/assets/make/ty-context.mk +48 -0
- package/assets/skills/context_development_engineer/SKILL.md +7 -4
- package/assets/skills/context_full_project_export/SKILL.md +13 -13
- package/assets/skills/context_harness_upgrade/SKILL.md +9 -9
- package/assets/skills/context_product_plan/SKILL.md +7 -4
- package/assets/skills/context_surface_contract/SKILL.md +168 -0
- package/assets/skills/context_uiux_design/SKILL.md +7 -4
- package/assets/skills/plan_acceptance_checklist_compiler/SKILL.md +427 -0
- package/assets/tools/validate_context.py +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/check-modularity.js +1 -1
- package/dist/commands/export-context.js +4 -4
- package/dist/commands/index.js +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/package-source.js +1 -1
- package/dist/commands/upgrade.js +1 -1
- package/dist/lib/config.js +2 -2
- package/dist/lib/constants.d.ts +1 -1
- package/dist/lib/constants.js +1 -1
- package/dist/lib/context-export.js +5 -5
- package/dist/lib/harness-root.d.ts +5 -0
- package/dist/lib/harness-root.js +32 -4
- package/dist/lib/legacy-managed-scan.d.ts +2 -0
- package/dist/lib/legacy-managed-scan.js +79 -0
- package/dist/lib/legacy-sdlc-migration.d.ts +2 -0
- package/dist/lib/legacy-sdlc-migration.js +189 -0
- package/dist/lib/managed-file.d.ts +18 -12
- package/dist/lib/managed-file.js +25 -14
- package/dist/lib/migrations.js +4 -2
- package/dist/lib/package-json-config.js +3 -3
- package/dist/lib/paths.d.ts +2 -2
- package/dist/lib/paths.js +2 -2
- package/dist/lib/sync-engine.js +33 -31
- package/dist/lib/validators.js +2 -2
- package/package.json +5 -5
- package/source-mappings.yaml +13 -13
- package/assets/make/sdlc-harness.mk +0 -48
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { listFiles, pathExists, readText } from "./fs.js";
|
|
3
|
+
import { packageAssetPath } from "./paths.js";
|
|
4
|
+
const LEGACY_MANAGED_ROOT = "pjsdlc_managed";
|
|
5
|
+
const CURRENT_MANAGED_ROOT = "ty-context-managed";
|
|
6
|
+
const LEGACY_MAKEFILE = "sdlc-harness.mk";
|
|
7
|
+
const CURRENT_MAKEFILE = "ty-context.mk";
|
|
8
|
+
export async function detectLegacyManagedDirectory(projectRoot, root, migration) {
|
|
9
|
+
const legacyRoot = path.join(projectRoot, root, LEGACY_MANAGED_ROOT);
|
|
10
|
+
const files = await listFiles(legacyRoot);
|
|
11
|
+
const items = [];
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
if (path.basename(file) === ".gitkeep") {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const relativeToLegacyRoot = path.relative(legacyRoot, file).split(path.sep).join("/");
|
|
17
|
+
const relativeToProject = path.relative(projectRoot, file).split(path.sep).join("/");
|
|
18
|
+
const classification = await classifyLegacyManagedFile(relativeToLegacyRoot);
|
|
19
|
+
if (classification.kind === "override") {
|
|
20
|
+
items.push(item(migration, "manual_required", relativeToProject, "Legacy skill overrides are no longer supported; move rules into a standalone project-local Skill."));
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (classification.kind === "unknown") {
|
|
24
|
+
items.push(item(migration, "manual_required", relativeToProject, "Legacy pjsdlc_managed content is not recognized as package-generated; review it manually."));
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const target = path.join(projectRoot, root, CURRENT_MANAGED_ROOT, ...classification.targetRelative.split("/"));
|
|
28
|
+
if (!(await pathExists(target))) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const assetContent = await readText(classification.assetPath);
|
|
32
|
+
if ((await readText(target)) !== assetContent) {
|
|
33
|
+
items.push(item(migration, "blocked", path.relative(projectRoot, target).split(path.sep).join("/"), `Cannot refresh legacy generated file because ${classification.targetRelative} already exists with non-package content.`));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return items;
|
|
37
|
+
}
|
|
38
|
+
async function classifyLegacyManagedFile(relative) {
|
|
39
|
+
if (relative.startsWith("override_skills/")) {
|
|
40
|
+
return { kind: "override" };
|
|
41
|
+
}
|
|
42
|
+
const mapped = legacyGeneratedMapping(relative);
|
|
43
|
+
if (!mapped) {
|
|
44
|
+
return { kind: "unknown" };
|
|
45
|
+
}
|
|
46
|
+
const assetPath = packageAssetPath(...mapped.assetSegments);
|
|
47
|
+
if (!(await pathExists(assetPath))) {
|
|
48
|
+
return { kind: "unknown" };
|
|
49
|
+
}
|
|
50
|
+
return { kind: "generated", targetRelative: mapped.targetRelative, assetPath };
|
|
51
|
+
}
|
|
52
|
+
function legacyGeneratedMapping(relative) {
|
|
53
|
+
if (relative === `make/${LEGACY_MAKEFILE}`) {
|
|
54
|
+
return { targetRelative: `make/${CURRENT_MAKEFILE}`, assetSegments: ["make", CURRENT_MAKEFILE] };
|
|
55
|
+
}
|
|
56
|
+
for (const [prefix, assetPrefix] of [
|
|
57
|
+
["agents/", "agents"],
|
|
58
|
+
["context_templates/", "context_templates"],
|
|
59
|
+
["skills/", "skills"],
|
|
60
|
+
["minimal_tools/", "tools"]
|
|
61
|
+
]) {
|
|
62
|
+
if (relative.startsWith(prefix)) {
|
|
63
|
+
const rest = relative.slice(prefix.length);
|
|
64
|
+
return { targetRelative: `${prefix}${rest}`, assetSegments: [assetPrefix, ...rest.split("/")] };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
function item(migration, status, pathLabel, message) {
|
|
70
|
+
return {
|
|
71
|
+
id: migration.id,
|
|
72
|
+
introducedIn: migration.introducedIn,
|
|
73
|
+
description: migration.description,
|
|
74
|
+
scope: migration.scope,
|
|
75
|
+
status,
|
|
76
|
+
path: pathLabel,
|
|
77
|
+
message
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { LEGACY_HARNESS_JSON_CONFIG_PATH, readHarnessRootConfigCandidates } from "./harness-root.js";
|
|
3
|
+
import { pathExists, readText, writeTextIfChanged } from "./fs.js";
|
|
4
|
+
import { detectLegacyManagedDirectory } from "./legacy-managed-scan.js";
|
|
5
|
+
import { HARNESS_JSON_CONFIG_PATH } from "./paths.js";
|
|
6
|
+
const LEGACY_MANAGED_ROOT = "pjsdlc_managed";
|
|
7
|
+
const CURRENT_MANAGED_ROOT = "ty-context-managed";
|
|
8
|
+
const LEGACY_MAKEFILE = "sdlc-harness.mk";
|
|
9
|
+
const CURRENT_MAKEFILE = "ty-context.mk";
|
|
10
|
+
export const legacySdlcHarnessMigration = {
|
|
11
|
+
id: "legacy-sdlc-harness-rename",
|
|
12
|
+
introducedIn: "0.2.54",
|
|
13
|
+
description: "Rename legacy sdlc-harness configuration and pjsdlc_managed package-managed paths.",
|
|
14
|
+
scope: "package.json, sdlc-harness.config.json, <harnessRoot>/config.yaml, old managed markers and pjsdlc_managed/**",
|
|
15
|
+
risk: "safe",
|
|
16
|
+
manualMessage: "Resolve legacy sdlc-harness conflicts or move old override rules into standalone project-local Skills.",
|
|
17
|
+
detect: detectLegacySdlcHarness,
|
|
18
|
+
apply: migrateLegacySdlcHarness,
|
|
19
|
+
verify: async () => undefined
|
|
20
|
+
};
|
|
21
|
+
async function detectLegacySdlcHarness(projectRoot, root, migration) {
|
|
22
|
+
const items = [];
|
|
23
|
+
const candidates = await readHarnessRootConfigCandidates(projectRoot);
|
|
24
|
+
const currentCandidates = candidates.filter((candidate) => !candidate.legacy);
|
|
25
|
+
const legacyCandidates = candidates.filter((candidate) => candidate.legacy);
|
|
26
|
+
const currentRoot = currentCandidates[0]?.harnessFolderName;
|
|
27
|
+
const distinctLegacyRoots = Array.from(new Set(legacyCandidates.map((candidate) => candidate.harnessFolderName)));
|
|
28
|
+
if (currentRoot && distinctLegacyRoots.some((legacyRoot) => legacyRoot !== currentRoot)) {
|
|
29
|
+
items.push(item(migration, "blocked", rootConflictPath(currentCandidates, legacyCandidates), "Current ty-context and legacy sdlc-harness root configuration disagree; choose the harness root manually before upgrade."));
|
|
30
|
+
}
|
|
31
|
+
else if (!currentRoot && distinctLegacyRoots.length > 1) {
|
|
32
|
+
items.push(item(migration, "blocked", rootConflictPath(currentCandidates, legacyCandidates), "Legacy sdlc-harness root sources disagree; choose one harness root manually before upgrade."));
|
|
33
|
+
}
|
|
34
|
+
items.push(...(await detectLegacyManagedDirectory(projectRoot, root, legacySdlcHarnessMigration)));
|
|
35
|
+
if (items.some((entry) => entry.status === "blocked")) {
|
|
36
|
+
return items;
|
|
37
|
+
}
|
|
38
|
+
if (await needsLegacySafeMigration(projectRoot, root, legacyCandidates.length > 0)) {
|
|
39
|
+
items.unshift(item(migration, "safe_pending", "legacy sdlc-harness surfaces", "Legacy sdlc-harness naming can be copied to ty-context configuration and refreshed by sync."));
|
|
40
|
+
}
|
|
41
|
+
return items;
|
|
42
|
+
}
|
|
43
|
+
async function migrateLegacySdlcHarness(projectRoot, root, report) {
|
|
44
|
+
await migratePackageJsonConfig(projectRoot, report);
|
|
45
|
+
await migrateJsonConfig(projectRoot, report);
|
|
46
|
+
await migrateConfigYamlPaths(projectRoot, root, report);
|
|
47
|
+
}
|
|
48
|
+
async function needsLegacySafeMigration(projectRoot, root, hasLegacyRootConfig) {
|
|
49
|
+
if (hasLegacyRootConfig && (await legacyPackageConfigNeedsCopy(projectRoot))) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (await legacyJsonConfigNeedsCopy(projectRoot)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (await configYamlHasLegacyManagedPaths(projectRoot, root)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (await hasLegacyManagedMarkers(projectRoot)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
async function legacyPackageConfigNeedsCopy(projectRoot) {
|
|
64
|
+
const packagePath = path.join(projectRoot, "package.json");
|
|
65
|
+
if (!(await pathExists(packagePath))) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const packageJson = parseJsonRecord(await readText(packagePath), "package.json");
|
|
69
|
+
return isJsonRecord(packageJson.sdlcHarness) && !isJsonRecord(packageJson.tyContext);
|
|
70
|
+
}
|
|
71
|
+
async function legacyJsonConfigNeedsCopy(projectRoot) {
|
|
72
|
+
const legacyPath = path.join(projectRoot, LEGACY_HARNESS_JSON_CONFIG_PATH);
|
|
73
|
+
const currentPath = path.join(projectRoot, HARNESS_JSON_CONFIG_PATH);
|
|
74
|
+
return (await pathExists(legacyPath)) && !(await pathExists(currentPath));
|
|
75
|
+
}
|
|
76
|
+
async function configYamlHasLegacyManagedPaths(projectRoot, root) {
|
|
77
|
+
const configPath = path.join(projectRoot, root, "config.yaml");
|
|
78
|
+
if (!(await pathExists(configPath))) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return hasLegacyManagedPath(await readText(configPath));
|
|
82
|
+
}
|
|
83
|
+
async function hasLegacyManagedMarkers(projectRoot) {
|
|
84
|
+
for (const relative of ["AGENTS.md", "Makefile", ".github/workflows/harness.yml"]) {
|
|
85
|
+
const target = path.join(projectRoot, ...relative.split("/"));
|
|
86
|
+
if ((await pathExists(target)) && hasLegacyMarker(await readText(target))) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
async function migratePackageJsonConfig(projectRoot, report) {
|
|
93
|
+
const packagePath = path.join(projectRoot, "package.json");
|
|
94
|
+
if (!(await pathExists(packagePath))) {
|
|
95
|
+
report.skipped.push("package.json#sdlcHarness");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const packageJson = parseJsonRecord(await readText(packagePath), "package.json");
|
|
99
|
+
if (!isJsonRecord(packageJson.sdlcHarness) || isJsonRecord(packageJson.tyContext)) {
|
|
100
|
+
report.skipped.push("package.json#sdlcHarness");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
packageJson.tyContext = { ...packageJson.sdlcHarness };
|
|
104
|
+
if (await writeTextIfChanged(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`)) {
|
|
105
|
+
report.changed.push("package.json#tyContext");
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
report.skipped.push("package.json#tyContext");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function migrateJsonConfig(projectRoot, report) {
|
|
112
|
+
const legacyPath = path.join(projectRoot, LEGACY_HARNESS_JSON_CONFIG_PATH);
|
|
113
|
+
const currentPath = path.join(projectRoot, HARNESS_JSON_CONFIG_PATH);
|
|
114
|
+
if (!(await pathExists(legacyPath))) {
|
|
115
|
+
report.skipped.push(LEGACY_HARNESS_JSON_CONFIG_PATH);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (await pathExists(currentPath)) {
|
|
119
|
+
report.skipped.push(HARNESS_JSON_CONFIG_PATH);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (await writeTextIfChanged(currentPath, await readText(legacyPath))) {
|
|
123
|
+
report.changed.push(HARNESS_JSON_CONFIG_PATH);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
report.skipped.push(HARNESS_JSON_CONFIG_PATH);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function migrateConfigYamlPaths(projectRoot, root, report) {
|
|
130
|
+
const relative = `${root}/config.yaml`;
|
|
131
|
+
const configPath = path.join(projectRoot, root, "config.yaml");
|
|
132
|
+
if (!(await pathExists(configPath))) {
|
|
133
|
+
report.skipped.push(relative);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const original = await readText(configPath);
|
|
137
|
+
const next = rewriteLegacyManagedPaths(original);
|
|
138
|
+
if (next === original) {
|
|
139
|
+
report.skipped.push(relative);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (await writeTextIfChanged(configPath, next)) {
|
|
143
|
+
report.changed.push(`${relative}#legacy-managed-paths`);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
report.skipped.push(`${relative}#legacy-managed-paths`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function rewriteLegacyManagedPaths(content) {
|
|
150
|
+
return content.replaceAll(LEGACY_MANAGED_ROOT, CURRENT_MANAGED_ROOT).replaceAll(LEGACY_MAKEFILE, CURRENT_MAKEFILE);
|
|
151
|
+
}
|
|
152
|
+
function hasLegacyManagedPath(content) {
|
|
153
|
+
return content.includes(LEGACY_MANAGED_ROOT) || content.includes(LEGACY_MAKEFILE);
|
|
154
|
+
}
|
|
155
|
+
function hasLegacyMarker(content) {
|
|
156
|
+
return [
|
|
157
|
+
"<!-- pjsdlc:sdlc-harness:begin -->",
|
|
158
|
+
"<!-- sdlc-harness:begin -->",
|
|
159
|
+
"# pjsdlc:sdlc-harness:make:begin",
|
|
160
|
+
"# sdlc-harness:make:begin",
|
|
161
|
+
"# pjsdlc:sdlc-harness:github-workflow:begin"
|
|
162
|
+
].some((marker) => content.includes(marker));
|
|
163
|
+
}
|
|
164
|
+
function rootConflictPath(currentCandidates, legacyCandidates) {
|
|
165
|
+
return [...currentCandidates, ...legacyCandidates]
|
|
166
|
+
.map((candidate) => `${candidate.source}=${candidate.harnessFolderName}`)
|
|
167
|
+
.join(", ");
|
|
168
|
+
}
|
|
169
|
+
function item(migrationId, status, pathLabel, message) {
|
|
170
|
+
return {
|
|
171
|
+
id: legacySdlcHarnessMigration.id,
|
|
172
|
+
introducedIn: legacySdlcHarnessMigration.introducedIn,
|
|
173
|
+
description: legacySdlcHarnessMigration.description,
|
|
174
|
+
scope: legacySdlcHarnessMigration.scope,
|
|
175
|
+
status,
|
|
176
|
+
path: pathLabel,
|
|
177
|
+
message
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function parseJsonRecord(content, pathLabel) {
|
|
181
|
+
const parsed = JSON.parse(content);
|
|
182
|
+
if (!isJsonRecord(parsed)) {
|
|
183
|
+
throw new Error(`${pathLabel} must contain a JSON object`);
|
|
184
|
+
}
|
|
185
|
+
return parsed;
|
|
186
|
+
}
|
|
187
|
+
function isJsonRecord(value) {
|
|
188
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
189
|
+
}
|
|
@@ -2,18 +2,24 @@ export interface ManagedBlockMarkers {
|
|
|
2
2
|
start: string;
|
|
3
3
|
end: string;
|
|
4
4
|
}
|
|
5
|
-
export declare const MANAGED_BLOCK_START = "<!--
|
|
6
|
-
export declare const MANAGED_BLOCK_END = "<!--
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const
|
|
9
|
-
export declare const
|
|
10
|
-
export declare const
|
|
11
|
-
export declare const
|
|
12
|
-
export declare const
|
|
13
|
-
export declare const
|
|
14
|
-
export declare const
|
|
15
|
-
export declare const
|
|
16
|
-
export declare const
|
|
5
|
+
export declare const MANAGED_BLOCK_START = "<!-- ty-context:managed:begin -->";
|
|
6
|
+
export declare const MANAGED_BLOCK_END = "<!-- ty-context:managed:end -->";
|
|
7
|
+
export declare const LEGACY_PJSDLC_MANAGED_BLOCK_START = "<!-- pjsdlc:sdlc-harness:begin -->";
|
|
8
|
+
export declare const LEGACY_PJSDLC_MANAGED_BLOCK_END = "<!-- pjsdlc:sdlc-harness:end -->";
|
|
9
|
+
export declare const LEGACY_SDLC_MANAGED_BLOCK_START = "<!-- sdlc-harness:begin -->";
|
|
10
|
+
export declare const LEGACY_SDLC_MANAGED_BLOCK_END = "<!-- sdlc-harness:end -->";
|
|
11
|
+
export declare const MAKEFILE_BLOCK_START = "# ty-context:make:begin";
|
|
12
|
+
export declare const MAKEFILE_BLOCK_END = "# ty-context:make:end";
|
|
13
|
+
export declare const LEGACY_PJSDLC_MAKEFILE_BLOCK_START = "# pjsdlc:sdlc-harness:make:begin";
|
|
14
|
+
export declare const LEGACY_PJSDLC_MAKEFILE_BLOCK_END = "# pjsdlc:sdlc-harness:make:end";
|
|
15
|
+
export declare const LEGACY_SDLC_MAKEFILE_BLOCK_START = "# sdlc-harness:make:begin";
|
|
16
|
+
export declare const LEGACY_SDLC_MAKEFILE_BLOCK_END = "# sdlc-harness:make:end";
|
|
17
|
+
export declare const GITHUB_WORKFLOW_BLOCK_START = "# ty-context:github-workflow:begin";
|
|
18
|
+
export declare const GITHUB_WORKFLOW_BLOCK_END = "# ty-context:github-workflow:end";
|
|
19
|
+
export declare const LEGACY_PJSDLC_GITHUB_WORKFLOW_BLOCK_START = "# pjsdlc:sdlc-harness:github-workflow:begin";
|
|
20
|
+
export declare const LEGACY_PJSDLC_GITHUB_WORKFLOW_BLOCK_END = "# pjsdlc:sdlc-harness:github-workflow:end";
|
|
21
|
+
export declare const MANAGED_METADATA_START = "<!-- ty-context-managed";
|
|
17
22
|
export declare const MANAGED_METADATA_END = "-->";
|
|
18
23
|
export declare const AGENTS_BLOCK_MARKERS: ManagedBlockMarkers[];
|
|
19
24
|
export declare const MAKEFILE_BLOCK_MARKERS: ManagedBlockMarkers[];
|
|
25
|
+
export declare const GITHUB_WORKFLOW_BLOCK_MARKERS: ManagedBlockMarkers[];
|
package/dist/lib/managed-file.js
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
|
-
export const MANAGED_BLOCK_START = "<!--
|
|
2
|
-
export const MANAGED_BLOCK_END = "<!--
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
6
|
-
export const
|
|
7
|
-
export const
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
10
|
-
export const
|
|
11
|
-
export const
|
|
12
|
-
export const
|
|
1
|
+
export const MANAGED_BLOCK_START = "<!-- ty-context:managed:begin -->";
|
|
2
|
+
export const MANAGED_BLOCK_END = "<!-- ty-context:managed:end -->";
|
|
3
|
+
export const LEGACY_PJSDLC_MANAGED_BLOCK_START = "<!-- pjsdlc:sdlc-harness:begin -->";
|
|
4
|
+
export const LEGACY_PJSDLC_MANAGED_BLOCK_END = "<!-- pjsdlc:sdlc-harness:end -->";
|
|
5
|
+
export const LEGACY_SDLC_MANAGED_BLOCK_START = "<!-- sdlc-harness:begin -->";
|
|
6
|
+
export const LEGACY_SDLC_MANAGED_BLOCK_END = "<!-- sdlc-harness:end -->";
|
|
7
|
+
export const MAKEFILE_BLOCK_START = "# ty-context:make:begin";
|
|
8
|
+
export const MAKEFILE_BLOCK_END = "# ty-context:make:end";
|
|
9
|
+
export const LEGACY_PJSDLC_MAKEFILE_BLOCK_START = "# pjsdlc:sdlc-harness:make:begin";
|
|
10
|
+
export const LEGACY_PJSDLC_MAKEFILE_BLOCK_END = "# pjsdlc:sdlc-harness:make:end";
|
|
11
|
+
export const LEGACY_SDLC_MAKEFILE_BLOCK_START = "# sdlc-harness:make:begin";
|
|
12
|
+
export const LEGACY_SDLC_MAKEFILE_BLOCK_END = "# sdlc-harness:make:end";
|
|
13
|
+
export const GITHUB_WORKFLOW_BLOCK_START = "# ty-context:github-workflow:begin";
|
|
14
|
+
export const GITHUB_WORKFLOW_BLOCK_END = "# ty-context:github-workflow:end";
|
|
15
|
+
export const LEGACY_PJSDLC_GITHUB_WORKFLOW_BLOCK_START = "# pjsdlc:sdlc-harness:github-workflow:begin";
|
|
16
|
+
export const LEGACY_PJSDLC_GITHUB_WORKFLOW_BLOCK_END = "# pjsdlc:sdlc-harness:github-workflow:end";
|
|
17
|
+
export const MANAGED_METADATA_START = "<!-- ty-context-managed";
|
|
13
18
|
export const MANAGED_METADATA_END = "-->";
|
|
14
19
|
export const AGENTS_BLOCK_MARKERS = [
|
|
15
20
|
{ start: MANAGED_BLOCK_START, end: MANAGED_BLOCK_END },
|
|
16
|
-
{ start:
|
|
21
|
+
{ start: LEGACY_PJSDLC_MANAGED_BLOCK_START, end: LEGACY_PJSDLC_MANAGED_BLOCK_END },
|
|
22
|
+
{ start: LEGACY_SDLC_MANAGED_BLOCK_START, end: LEGACY_SDLC_MANAGED_BLOCK_END }
|
|
17
23
|
];
|
|
18
24
|
export const MAKEFILE_BLOCK_MARKERS = [
|
|
19
25
|
{ start: MAKEFILE_BLOCK_START, end: MAKEFILE_BLOCK_END },
|
|
20
|
-
{ start:
|
|
26
|
+
{ start: LEGACY_PJSDLC_MAKEFILE_BLOCK_START, end: LEGACY_PJSDLC_MAKEFILE_BLOCK_END },
|
|
27
|
+
{ start: LEGACY_SDLC_MAKEFILE_BLOCK_START, end: LEGACY_SDLC_MAKEFILE_BLOCK_END }
|
|
28
|
+
];
|
|
29
|
+
export const GITHUB_WORKFLOW_BLOCK_MARKERS = [
|
|
30
|
+
{ start: GITHUB_WORKFLOW_BLOCK_START, end: GITHUB_WORKFLOW_BLOCK_END },
|
|
31
|
+
{ start: LEGACY_PJSDLC_GITHUB_WORKFLOW_BLOCK_START, end: LEGACY_PJSDLC_GITHUB_WORKFLOW_BLOCK_END }
|
|
21
32
|
];
|
package/dist/lib/migrations.js
CHANGED
|
@@ -7,11 +7,13 @@ import { defaultConfig, readConfig } from "./config.js";
|
|
|
7
7
|
import { createDesignMdIfMissing, DESIGN_MD_PATH } from "./design-md.js";
|
|
8
8
|
import { ensureDir, listFiles, pathExists, readText, writeTextIfChanged } from "./fs.js";
|
|
9
9
|
import { harnessConfigPath, harnessRoot } from "./harness-root.js";
|
|
10
|
+
import { legacySdlcHarnessMigration } from "./legacy-sdlc-migration.js";
|
|
10
11
|
import { stringifyYaml } from "./yaml.js";
|
|
11
12
|
async function verifyNoop() {
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
export const migrations = [
|
|
16
|
+
legacySdlcHarnessMigration,
|
|
15
17
|
{
|
|
16
18
|
id: "schema-v4-config-refresh",
|
|
17
19
|
introducedIn: "0.2.0",
|
|
@@ -71,7 +73,7 @@ export const migrations = [
|
|
|
71
73
|
id: "deprecated-skill-overrides",
|
|
72
74
|
introducedIn: "0.2.0",
|
|
73
75
|
description: "Report deprecated managed skill overrides that must move to standalone project-local Skills.",
|
|
74
|
-
scope: "<harnessRoot>/
|
|
76
|
+
scope: "<harnessRoot>/ty-context-managed/override_skills/**",
|
|
75
77
|
risk: "manual",
|
|
76
78
|
manualMessage: "Move override files into standalone project-local Skills before running sync.",
|
|
77
79
|
detect: detectDeprecatedSkillOverrides,
|
|
@@ -355,7 +357,7 @@ async function migrateConfig(projectRoot, root, report) {
|
|
|
355
357
|
}
|
|
356
358
|
}
|
|
357
359
|
async function detectDeprecatedSkillOverrides(projectRoot, root, migration) {
|
|
358
|
-
const overrideRoot = path.join(projectRoot, root, "
|
|
360
|
+
const overrideRoot = path.join(projectRoot, root, "ty-context-managed", "override_skills");
|
|
359
361
|
if (!(await pathExists(overrideRoot))) {
|
|
360
362
|
return [];
|
|
361
363
|
}
|
|
@@ -7,7 +7,7 @@ export async function packageHarnessRoot(projectRoot) {
|
|
|
7
7
|
return undefined;
|
|
8
8
|
}
|
|
9
9
|
const packageJson = parsePackageJson(await readText(packagePath));
|
|
10
|
-
const config = packageJson.
|
|
10
|
+
const config = packageJson.tyContext;
|
|
11
11
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
12
12
|
return undefined;
|
|
13
13
|
}
|
|
@@ -18,13 +18,13 @@ export async function writePackageHarnessRoot(projectRoot, folderName) {
|
|
|
18
18
|
const normalized = normalizeHarnessFolderName(folderName);
|
|
19
19
|
const packagePath = path.join(projectRoot, "package.json");
|
|
20
20
|
const packageJson = (await pathExists(packagePath)) ? parsePackageJson(await readText(packagePath)) : {};
|
|
21
|
-
const existingConfig = packageJson.
|
|
21
|
+
const existingConfig = packageJson.tyContext;
|
|
22
22
|
const nextConfig = existingConfig && typeof existingConfig === "object" && !Array.isArray(existingConfig)
|
|
23
23
|
? { ...existingConfig, harnessFolderName: normalized }
|
|
24
24
|
: { harnessFolderName: normalized };
|
|
25
25
|
const next = {
|
|
26
26
|
...packageJson,
|
|
27
|
-
|
|
27
|
+
tyContext: nextConfig
|
|
28
28
|
};
|
|
29
29
|
return writeTextIfChanged(packagePath, `${JSON.stringify(next, null, 2)}\n`);
|
|
30
30
|
}
|
package/dist/lib/paths.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const SOURCE_MAPPINGS_PATH = "packages/
|
|
1
|
+
export declare const SOURCE_MAPPINGS_PATH = "packages/ty-context/source-mappings.yaml";
|
|
2
2
|
export declare const DEFAULT_HARNESS_ROOT = ".agent";
|
|
3
|
-
export declare const HARNESS_JSON_CONFIG_PATH = "
|
|
3
|
+
export declare const HARNESS_JSON_CONFIG_PATH = "ty-context.config.json";
|
|
4
4
|
export declare function packageRoot(): string;
|
|
5
5
|
export declare function packageAssetPath(...segments: string[]): string;
|
package/dist/lib/paths.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
export const SOURCE_MAPPINGS_PATH = "packages/
|
|
3
|
+
export const SOURCE_MAPPINGS_PATH = "packages/ty-context/source-mappings.yaml";
|
|
4
4
|
export const DEFAULT_HARNESS_ROOT = ".agent";
|
|
5
|
-
export const HARNESS_JSON_CONFIG_PATH = "
|
|
5
|
+
export const HARNESS_JSON_CONFIG_PATH = "ty-context.config.json";
|
|
6
6
|
export function packageRoot() {
|
|
7
7
|
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
8
8
|
}
|
package/dist/lib/sync-engine.js
CHANGED
|
@@ -3,7 +3,7 @@ import { promises as fs } from "node:fs";
|
|
|
3
3
|
import { readConfig } from "./config.js";
|
|
4
4
|
import { harnessPath, harnessRoot } from "./harness-root.js";
|
|
5
5
|
import { copyTree, listFiles, pathExists, readText, writeTextIfChanged } from "./fs.js";
|
|
6
|
-
import { AGENTS_BLOCK_MARKERS, GITHUB_WORKFLOW_BLOCK_END, GITHUB_WORKFLOW_BLOCK_START, MAKEFILE_BLOCK_END, MAKEFILE_BLOCK_MARKERS, MAKEFILE_BLOCK_START, MANAGED_BLOCK_END, MANAGED_BLOCK_START } from "./managed-file.js";
|
|
6
|
+
import { AGENTS_BLOCK_MARKERS, GITHUB_WORKFLOW_BLOCK_END, GITHUB_WORKFLOW_BLOCK_MARKERS, GITHUB_WORKFLOW_BLOCK_START, MAKEFILE_BLOCK_END, MAKEFILE_BLOCK_MARKERS, MAKEFILE_BLOCK_START, MANAGED_BLOCK_END, MANAGED_BLOCK_START } from "./managed-file.js";
|
|
7
7
|
import { packageAssetPath } from "./paths.js";
|
|
8
8
|
import { assertSupportedSchema } from "./schema-guard.js";
|
|
9
9
|
import { createUpgradePlan, formatUpgradePlan, hasUpgradePlanWork } from "./migrations.js";
|
|
@@ -50,20 +50,20 @@ async function syncManagedFile(projectRoot, root, managedFile, report) {
|
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
const managedPath = normalizeManagedPath(managedFile.path);
|
|
53
|
-
if (managedPath === harnessPath(root, "
|
|
53
|
+
if (managedPath === harnessPath(root, "ty-context-managed", "templates")) {
|
|
54
54
|
await syncTree(packageAssetPath("templates"), destination, report);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
-
if (managedPath === harnessPath(root, "
|
|
57
|
+
if (managedPath === harnessPath(root, "ty-context-managed", "context_templates")) {
|
|
58
58
|
await syncTree(packageAssetPath("context_templates"), destination, report, { prune: true });
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
|
-
if (managedPath === harnessPath(root, "
|
|
61
|
+
if (managedPath === harnessPath(root, "ty-context-managed", "policies")) {
|
|
62
62
|
await syncTree(packageAssetPath("policies"), destination, report);
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
-
if (managedPath === harnessPath(root, "
|
|
66
|
-
await syncFile(packageAssetPath("make", "
|
|
65
|
+
if (managedPath === harnessPath(root, "ty-context-managed", "make", "ty-context.mk")) {
|
|
66
|
+
await syncFile(packageAssetPath("make", "ty-context.mk"), destination, report, "skip-if-missing");
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
if (managedFile.path === "tools") {
|
|
@@ -118,7 +118,7 @@ function renderAgentsCore(content, root) {
|
|
|
118
118
|
async function syncMakefileInclude(destination, root, report) {
|
|
119
119
|
const existing = (await pathExists(destination)) ? await readText(destination) : "";
|
|
120
120
|
const resetDefaultGoal = shouldResetMakeDefaultGoal(existing);
|
|
121
|
-
const includePath = `${root.replace(/\\/g, "/")}/
|
|
121
|
+
const includePath = `${root.replace(/\\/g, "/")}/ty-context-managed/make/ty-context.mk`;
|
|
122
122
|
const blockLines = [
|
|
123
123
|
MAKEFILE_BLOCK_START,
|
|
124
124
|
"# Included before project targets so project recipes win on name conflicts.",
|
|
@@ -264,22 +264,31 @@ async function syncSkillsTree(source, destination, report) {
|
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
async function blockDeprecatedSkillOverrides(projectRoot, root, report) {
|
|
267
|
-
const overrideRoot
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
267
|
+
for (const overrideRoot of skillOverrideRoots(projectRoot, root)) {
|
|
268
|
+
if (!(await pathExists(overrideRoot.absolute))) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const deprecatedFiles = (await listFiles(overrideRoot.absolute))
|
|
272
|
+
.filter((file) => path.basename(file) !== ".gitkeep")
|
|
273
|
+
.map((file) => path.relative(overrideRoot.absolute, file).split(path.sep).join("/"))
|
|
274
|
+
.sort();
|
|
275
|
+
if (deprecatedFiles.length === 0) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
report.blocked.push(`${overrideRoot.relative}: Skill overrides are no longer supported. Move these rules into a separate project-local Skill such as ${root.replace(/\\/g, "/")}/skills/product_plan/SKILL.md, ${root.replace(/\\/g, "/")}/skills/uiux_design/SKILL.md or ${root.replace(/\\/g, "/")}/skills/development_engineer/SKILL.md. Deprecated files: ${deprecatedFiles.join(", ")}`);
|
|
277
279
|
}
|
|
278
|
-
const relativeRoot = path.join(root, "pjsdlc_managed", "override_skills").split(path.sep).join("/");
|
|
279
|
-
report.blocked.push(`${relativeRoot}: Skill overrides are no longer supported. Move these rules into a separate project-local Skill such as ${root.replace(/\\/g, "/")}/skills/product_plan/SKILL.md, ${root.replace(/\\/g, "/")}/skills/uiux_design/SKILL.md or ${root.replace(/\\/g, "/")}/skills/development_engineer/SKILL.md. Deprecated files: ${deprecatedFiles.join(", ")}`);
|
|
280
280
|
}
|
|
281
|
-
function
|
|
282
|
-
return
|
|
281
|
+
function skillOverrideRoots(projectRoot, root) {
|
|
282
|
+
return [
|
|
283
|
+
{
|
|
284
|
+
absolute: path.join(projectRoot, root, "ty-context-managed", "override_skills"),
|
|
285
|
+
relative: path.join(root, "ty-context-managed", "override_skills").split(path.sep).join("/")
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
absolute: path.join(projectRoot, root, "pjsdlc_managed", "override_skills"),
|
|
289
|
+
relative: path.join(root, "pjsdlc_managed", "override_skills").split(path.sep).join("/")
|
|
290
|
+
}
|
|
291
|
+
];
|
|
283
292
|
}
|
|
284
293
|
async function syncFile(source, destination, report, missingMode) {
|
|
285
294
|
if (!(await pathExists(source))) {
|
|
@@ -331,18 +340,11 @@ async function syncGithubWorkflow(source, destination, relativePath, report) {
|
|
|
331
340
|
report.skipped.push(`${relativePath}: customized`);
|
|
332
341
|
}
|
|
333
342
|
function workflowMarkerState(content) {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
const hasStart = startIndex >= 0;
|
|
337
|
-
const hasEnd = endIndex >= 0;
|
|
338
|
-
if (!hasStart && !hasEnd) {
|
|
343
|
+
const found = findManagedBlock(content, GITHUB_WORKFLOW_BLOCK_MARKERS);
|
|
344
|
+
if (found.status === "missing") {
|
|
339
345
|
return "missing";
|
|
340
346
|
}
|
|
341
|
-
if (
|
|
342
|
-
return "invalid";
|
|
343
|
-
}
|
|
344
|
-
if (content.indexOf(GITHUB_WORKFLOW_BLOCK_START, startIndex + GITHUB_WORKFLOW_BLOCK_START.length) >= 0 ||
|
|
345
|
-
content.indexOf(GITHUB_WORKFLOW_BLOCK_END, endIndex + GITHUB_WORKFLOW_BLOCK_END.length) >= 0) {
|
|
347
|
+
if (found.status === "invalid") {
|
|
346
348
|
return "invalid";
|
|
347
349
|
}
|
|
348
350
|
return "managed";
|
package/dist/lib/validators.js
CHANGED
|
@@ -160,7 +160,7 @@ async function validateContext(projectRoot) {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
else if (schemaRequiresContextManifest(schemaVersion)) {
|
|
163
|
-
errors.push("project_context/context.toml is missing; run
|
|
163
|
+
errors.push("project_context/context.toml is missing; run ty-context upgrade to create the Schema v4 Context graph manifest");
|
|
164
164
|
}
|
|
165
165
|
const contextFiles = (await listFiles(projectContextRoot))
|
|
166
166
|
.filter((file) => file.endsWith(".md"))
|
|
@@ -243,7 +243,7 @@ async function validateContextManifest(projectRoot, manifest, manifestRoles, err
|
|
|
243
243
|
async function addManifestRole(projectRoot, roles, rawPath, role, source, errors) {
|
|
244
244
|
const relative = normalizeContextPath(rawPath);
|
|
245
245
|
if (looksLikeExportArtifact(relative)) {
|
|
246
|
-
errors.push(`project_context/context.toml ${source} must not reference temporary export artifact ${rawPath}; export artifacts belong in tmp/
|
|
246
|
+
errors.push(`project_context/context.toml ${source} must not reference temporary export artifact ${rawPath}; export artifacts belong in tmp/ty-context/context-exports/** and must not be registered as Context graph nodes or implementation-index`);
|
|
247
247
|
return;
|
|
248
248
|
}
|
|
249
249
|
if (!relative.startsWith("project_context/") || !relative.endsWith(".md")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-tiny-context-harness",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.55",
|
|
4
4
|
"description": "Minimal project memory and validation harness for AI coding agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Seven128",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/Seven128/project-tiny-context-harness.git",
|
|
11
|
-
"directory": "packages/
|
|
11
|
+
"directory": "packages/ty-context"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/Seven128/project-tiny-context-harness/issues"
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"developer-tools",
|
|
34
34
|
"developer-productivity",
|
|
35
35
|
"cli",
|
|
36
|
-
"
|
|
36
|
+
"ty-context",
|
|
37
37
|
"workflow"
|
|
38
38
|
],
|
|
39
39
|
"type": "module",
|
|
40
40
|
"bin": {
|
|
41
|
-
"
|
|
41
|
+
"ty-context": "dist/cli.js"
|
|
42
42
|
},
|
|
43
43
|
"files": [
|
|
44
44
|
"README.md",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
52
52
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
53
|
-
"test": "npm run build && node --test ../../tests/
|
|
53
|
+
"test": "npm run build && node --test ../../tests/ty-context/*.test.mjs",
|
|
54
54
|
"prepack": "npm run build"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|