youmd 0.6.21 → 0.6.26
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/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +0 -1
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +283 -184
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +21 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/keys.d.ts.map +1 -1
- package/dist/commands/keys.js +3 -3
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +3 -3
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/login.d.ts +29 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +256 -127
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logs.js +0 -1
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/mcp.d.ts +14 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +284 -28
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/private.d.ts.map +1 -1
- package/dist/commands/private.js +6 -5
- package/dist/commands/private.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +9 -5
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +0 -1
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/pull.d.ts +24 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +114 -14
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts +1 -0
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +33 -13
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/register.d.ts +6 -0
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +179 -79
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/skill.d.ts.map +1 -1
- package/dist/commands/skill.js +121 -50
- package/dist/commands/skill.js.map +1 -1
- package/dist/commands/stack.d.ts +16 -0
- package/dist/commands/stack.d.ts.map +1 -0
- package/dist/commands/stack.js +670 -0
- package/dist/commands/stack.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +25 -5
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.d.ts +23 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +193 -12
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +21 -16
- package/dist/commands/whoami.js.map +1 -1
- package/dist/create.js +0 -0
- package/dist/index.js +50 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +94 -5
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +122 -5
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/ascii.d.ts +1 -0
- package/dist/lib/ascii.d.ts.map +1 -1
- package/dist/lib/ascii.js +9 -26
- package/dist/lib/ascii.js.map +1 -1
- package/dist/lib/canonical-json.d.ts +8 -0
- package/dist/lib/canonical-json.d.ts.map +1 -0
- package/dist/lib/canonical-json.js +29 -0
- package/dist/lib/canonical-json.js.map +1 -0
- package/dist/lib/capability-router.d.ts +58 -0
- package/dist/lib/capability-router.d.ts.map +1 -0
- package/dist/lib/capability-router.js +54 -0
- package/dist/lib/capability-router.js.map +1 -0
- package/dist/lib/compiler.d.ts +32 -0
- package/dist/lib/compiler.d.ts.map +1 -1
- package/dist/lib/compiler.js +150 -0
- package/dist/lib/compiler.js.map +1 -1
- package/dist/lib/config.d.ts +71 -35
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +334 -124
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/decompile.d.ts +6 -1
- package/dist/lib/decompile.d.ts.map +1 -1
- package/dist/lib/decompile.js +120 -11
- package/dist/lib/decompile.js.map +1 -1
- package/dist/lib/hash.d.ts.map +1 -1
- package/dist/lib/hash.js +2 -19
- package/dist/lib/hash.js.map +1 -1
- package/dist/lib/heartbeat.d.ts +38 -0
- package/dist/lib/heartbeat.d.ts.map +1 -0
- package/dist/lib/heartbeat.js +70 -0
- package/dist/lib/heartbeat.js.map +1 -0
- package/dist/lib/host-link.d.ts +123 -0
- package/dist/lib/host-link.d.ts.map +1 -0
- package/dist/lib/host-link.js +350 -0
- package/dist/lib/host-link.js.map +1 -0
- package/dist/lib/merge.d.ts +50 -0
- package/dist/lib/merge.d.ts.map +1 -0
- package/dist/lib/merge.js +167 -0
- package/dist/lib/merge.js.map +1 -0
- package/dist/lib/onboarding.d.ts +17 -0
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +224 -207
- package/dist/lib/onboarding.js.map +1 -1
- package/dist/lib/project.d.ts +5 -0
- package/dist/lib/project.d.ts.map +1 -1
- package/dist/lib/project.js +8 -0
- package/dist/lib/project.js.map +1 -1
- package/dist/lib/projectContext.d.ts +201 -0
- package/dist/lib/projectContext.d.ts.map +1 -0
- package/dist/lib/projectContext.js +597 -0
- package/dist/lib/projectContext.js.map +1 -0
- package/dist/lib/render.d.ts +38 -1
- package/dist/lib/render.d.ts.map +1 -1
- package/dist/lib/render.js +83 -18
- package/dist/lib/render.js.map +1 -1
- package/dist/lib/skill-catalog.d.ts.map +1 -1
- package/dist/lib/skill-catalog.js +22 -2
- package/dist/lib/skill-catalog.js.map +1 -1
- package/dist/lib/skills.d.ts +20 -5
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +116 -58
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/stackEval.d.ts +87 -0
- package/dist/lib/stackEval.d.ts.map +1 -0
- package/dist/lib/stackEval.js +233 -0
- package/dist/lib/stackEval.js.map +1 -0
- package/dist/lib/stackImprove.d.ts +49 -0
- package/dist/lib/stackImprove.d.ts.map +1 -0
- package/dist/lib/stackImprove.js +313 -0
- package/dist/lib/stackImprove.js.map +1 -0
- package/dist/lib/stackInstall.d.ts +59 -0
- package/dist/lib/stackInstall.d.ts.map +1 -0
- package/dist/lib/stackInstall.js +162 -0
- package/dist/lib/stackInstall.js.map +1 -0
- package/dist/lib/stackSafety.d.ts +126 -0
- package/dist/lib/stackSafety.d.ts.map +1 -0
- package/dist/lib/stackSafety.js +486 -0
- package/dist/lib/stackSafety.js.map +1 -0
- package/dist/lib/stackUpdate.d.ts +44 -0
- package/dist/lib/stackUpdate.d.ts.map +1 -0
- package/dist/lib/stackUpdate.js +256 -0
- package/dist/lib/stackUpdate.js.map +1 -0
- package/dist/lib/stream.d.ts +116 -0
- package/dist/lib/stream.d.ts.map +1 -0
- package/dist/lib/stream.js +368 -0
- package/dist/lib/stream.js.map +1 -0
- package/dist/lib/youstack.d.ts +219 -0
- package/dist/lib/youstack.d.ts.map +1 -0
- package/dist/lib/youstack.js +1049 -0
- package/dist/lib/youstack.js.map +1 -0
- package/dist/mcp/registry.d.ts +133 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +833 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/server.d.ts +106 -5
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +851 -498
- package/dist/mcp/server.js.map +1 -1
- package/dist/postinstall.js +11 -2
- package/dist/postinstall.js.map +1 -1
- package/dist/you.d.ts +1 -0
- package/dist/you.js +40 -4
- package/dist/you.js.map +1 -1
- package/examples/youstack-bamfstack-public/docs/quickstart.md +34 -0
- package/examples/youstack-bamfstack-public/prompts/creator-draft-starter.md +15 -0
- package/examples/youstack-bamfstack-public/skills/bamfstack-start/SKILL.md +36 -0
- package/examples/youstack-bamfstack-public/tests/smoke.md +19 -0
- package/examples/youstack-bamfstack-public/workflows/creator-growth-workflow.md +22 -0
- package/examples/youstack-bamfstack-public/youstack.json +172 -0
- package/examples/youstack-personal/docs/quickstart.md +10 -0
- package/examples/youstack-personal/skills/youstack-start/SKILL.md +9 -0
- package/examples/youstack-personal/tests/smoke.md +9 -0
- package/examples/youstack-personal/workflows/startup.md +7 -0
- package/examples/youstack-personal/youstack.json +123 -0
- package/package.json +3 -3
- package/skills/claude-md-generator.md +1 -1
- package/skills/meta-improve.md +8 -3
- package/skills/proactive-context-fill.md +1 -1
- package/skills/youstack-maintainer.md +128 -0
- package/skills/youstack-start.md +78 -0
|
@@ -0,0 +1,1049 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.tierRefusalMessage = exports.tierAllows = exports.parseSafetyContract = exports.runStackGuard = exports.KNOWN_API_SCOPES = void 0;
|
|
37
|
+
exports.isCanonicalYouStackManifestPath = isCanonicalYouStackManifestPath;
|
|
38
|
+
exports.findYouStackManifestCandidates = findYouStackManifestCandidates;
|
|
39
|
+
exports.resolveYouStackManifestPath = resolveYouStackManifestPath;
|
|
40
|
+
exports.validateYouStackManifest = validateYouStackManifest;
|
|
41
|
+
exports.loadYouStackManifest = loadYouStackManifest;
|
|
42
|
+
exports.getYouStackReadiness = getYouStackReadiness;
|
|
43
|
+
exports.sha256File = sha256File;
|
|
44
|
+
exports.getYouStackCapabilities = getYouStackCapabilities;
|
|
45
|
+
exports.runYouStackSmoke = runYouStackSmoke;
|
|
46
|
+
exports.runYouStackDoctor = runYouStackDoctor;
|
|
47
|
+
exports.routeYouStackRequest = routeYouStackRequest;
|
|
48
|
+
exports.checkTierAllows = checkTierAllows;
|
|
49
|
+
exports.normalizeYouStackHost = normalizeYouStackHost;
|
|
50
|
+
exports.defaultAdapterPath = defaultAdapterPath;
|
|
51
|
+
exports.generateYouStackAdapterContent = generateYouStackAdapterContent;
|
|
52
|
+
exports.planYouStackAdapterLinks = planYouStackAdapterLinks;
|
|
53
|
+
exports.linkYouStackAdapters = linkYouStackAdapters;
|
|
54
|
+
exports.verifyYouStackClaudeDiscovery = verifyYouStackClaudeDiscovery;
|
|
55
|
+
const crypto = __importStar(require("crypto"));
|
|
56
|
+
const child_process_1 = require("child_process");
|
|
57
|
+
const fs = __importStar(require("fs"));
|
|
58
|
+
const path = __importStar(require("path"));
|
|
59
|
+
const host_link_1 = require("./host-link");
|
|
60
|
+
const stackSafety_1 = require("./stackSafety");
|
|
61
|
+
/**
|
|
62
|
+
* Canonical API scope vocabulary — mirrors convex/lib/scopes.ts API_SCOPES.
|
|
63
|
+
* P19 (2026-06-12): used by stack doctor to validate requiresScopes entries.
|
|
64
|
+
*/
|
|
65
|
+
exports.KNOWN_API_SCOPES = [
|
|
66
|
+
"read:public",
|
|
67
|
+
"read:private",
|
|
68
|
+
"write:bundle",
|
|
69
|
+
"write:memories",
|
|
70
|
+
"vault",
|
|
71
|
+
];
|
|
72
|
+
const VALID_VISIBILITY = new Set([
|
|
73
|
+
"private",
|
|
74
|
+
"scoped-link",
|
|
75
|
+
"public-open",
|
|
76
|
+
"team",
|
|
77
|
+
]);
|
|
78
|
+
const SHELL_SAFE_IDENTIFIER_RE = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
79
|
+
const SINGLE_LINE_METADATA_RE = /^[^\r\n\t]+$/;
|
|
80
|
+
const BUILT_IN_CAPABILITIES = [
|
|
81
|
+
{
|
|
82
|
+
id: "manifest.inspect",
|
|
83
|
+
intent: "inspect the local YouStack manifest",
|
|
84
|
+
localOnly: true,
|
|
85
|
+
mutationPolicy: "read_only",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "manifest.smoke",
|
|
89
|
+
intent: "run read-only local YouStack smoke validation",
|
|
90
|
+
localOnly: true,
|
|
91
|
+
mutationPolicy: "read_only",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "stack.diagnose",
|
|
95
|
+
intent: "run read-only stack health diagnostics for manifest bloat, capability routing, adapter drift, update hygiene, and public-readiness gaps",
|
|
96
|
+
skill: "youstack-maintainer",
|
|
97
|
+
localOnly: true,
|
|
98
|
+
mutationPolicy: "read_only",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "stack.improve",
|
|
102
|
+
intent: "inspect usage signals, evals, failures, corrections, and repo diffs to improve stack skills and workflows",
|
|
103
|
+
localOnly: true,
|
|
104
|
+
mutationPolicy: "write_local",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "stack.update",
|
|
108
|
+
intent: "check the stack update channel and refresh allowed local skills, workflows, docs, prompts, evals, and adapter files",
|
|
109
|
+
localOnly: true,
|
|
110
|
+
mutationPolicy: "write_local",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "stack.maintain",
|
|
114
|
+
intent: "organize, deduplicate, improve, and self-update named YouStacks and their skills using the bundled youstack-maintainer skill",
|
|
115
|
+
skill: "youstack-maintainer",
|
|
116
|
+
localOnly: true,
|
|
117
|
+
mutationPolicy: "write_local",
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
122
|
+
}
|
|
123
|
+
function expectString(obj, field, errors, options = {}) {
|
|
124
|
+
const value = obj[field];
|
|
125
|
+
if (value === undefined || value === null) {
|
|
126
|
+
if (!options.optional)
|
|
127
|
+
errors.push(`missing required field: ${field}`);
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
131
|
+
errors.push(`${field} must be a non-empty string`);
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
if (options.exact && value !== options.exact) {
|
|
135
|
+
errors.push(`${field} must be ${options.exact}`);
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
function expectStringField(obj, field, label, errors, options = {}) {
|
|
140
|
+
const value = obj[field];
|
|
141
|
+
if (value === undefined || value === null) {
|
|
142
|
+
if (!options.optional)
|
|
143
|
+
errors.push(`missing required field: ${label}`);
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
147
|
+
errors.push(`${label} must be a non-empty string`);
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
if (options.exact && value !== options.exact) {
|
|
151
|
+
errors.push(`${label} must be ${options.exact}`);
|
|
152
|
+
}
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
function isSafeRelativePath(value) {
|
|
156
|
+
if (!value || path.isAbsolute(value) || value.startsWith("~"))
|
|
157
|
+
return false;
|
|
158
|
+
const parts = value.split(/[\\/]+/);
|
|
159
|
+
return !parts.includes("..");
|
|
160
|
+
}
|
|
161
|
+
function validateOptionalStringArray(value, field, errors) {
|
|
162
|
+
if (value[field] === undefined)
|
|
163
|
+
return;
|
|
164
|
+
if (!Array.isArray(value[field])) {
|
|
165
|
+
errors.push(`${field} must be an array`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
for (const [index, item] of value[field].entries()) {
|
|
169
|
+
if (typeof item !== "string" || item.trim().length === 0) {
|
|
170
|
+
errors.push(`${field}[${index}] must be a non-empty string`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function isShellSafeIdentifier(value) {
|
|
175
|
+
return SHELL_SAFE_IDENTIFIER_RE.test(value);
|
|
176
|
+
}
|
|
177
|
+
function isSingleLineMetadata(value) {
|
|
178
|
+
return SINGLE_LINE_METADATA_RE.test(value);
|
|
179
|
+
}
|
|
180
|
+
function parseJsonFile(filePath) {
|
|
181
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
182
|
+
return JSON.parse(raw);
|
|
183
|
+
}
|
|
184
|
+
/** Canonical layout: stacks/<slug>/youstack.json inside the user's repo/brain. */
|
|
185
|
+
const CANONICAL_STACKS_DIRNAME = "stacks";
|
|
186
|
+
/** Legacy layout (still readable): youstacks/<slug>/youstack.json. */
|
|
187
|
+
const LEGACY_STACKS_DIRNAME = "youstacks";
|
|
188
|
+
function isCanonicalYouStackManifestPath(manifestPath) {
|
|
189
|
+
const resolved = path.resolve(manifestPath);
|
|
190
|
+
if (path.basename(resolved) !== "youstack.json")
|
|
191
|
+
return false;
|
|
192
|
+
const slugDir = path.dirname(resolved);
|
|
193
|
+
const stacksDir = path.dirname(slugDir);
|
|
194
|
+
if (slugDir === stacksDir)
|
|
195
|
+
return false;
|
|
196
|
+
return path.basename(stacksDir) === CANONICAL_STACKS_DIRNAME;
|
|
197
|
+
}
|
|
198
|
+
function collectStacksDirManifests(dir, stacksDirName, candidates) {
|
|
199
|
+
const stacksDir = path.join(dir, stacksDirName);
|
|
200
|
+
if (!fs.existsSync(stacksDir) || !fs.statSync(stacksDir).isDirectory())
|
|
201
|
+
return;
|
|
202
|
+
for (const entry of fs.readdirSync(stacksDir, { withFileTypes: true })) {
|
|
203
|
+
if (!entry.isDirectory())
|
|
204
|
+
continue;
|
|
205
|
+
const manifest = path.join(stacksDir, entry.name, "youstack.json");
|
|
206
|
+
if (fs.existsSync(manifest) && fs.statSync(manifest).isFile()) {
|
|
207
|
+
candidates.push(manifest);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function findYouStackManifestCandidates(startDir) {
|
|
212
|
+
const candidates = [];
|
|
213
|
+
let dir = path.resolve(startDir);
|
|
214
|
+
const root = path.parse(dir).root;
|
|
215
|
+
while (dir !== root) {
|
|
216
|
+
for (const candidate of [
|
|
217
|
+
path.join(dir, "youstack.json"),
|
|
218
|
+
path.join(dir, ".you", "youstack.json"),
|
|
219
|
+
]) {
|
|
220
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
221
|
+
candidates.push(candidate);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Canonical layout first-class: stacks/<slug>/youstack.json
|
|
225
|
+
collectStacksDirManifests(dir, CANONICAL_STACKS_DIRNAME, candidates);
|
|
226
|
+
// Legacy layout kept readable: youstacks/<slug>/youstack.json
|
|
227
|
+
collectStacksDirManifests(dir, LEGACY_STACKS_DIRNAME, candidates);
|
|
228
|
+
dir = path.dirname(dir);
|
|
229
|
+
}
|
|
230
|
+
return [...new Set(candidates)].sort((a, b) => Number(isCanonicalYouStackManifestPath(b)) -
|
|
231
|
+
Number(isCanonicalYouStackManifestPath(a)) || a.localeCompare(b));
|
|
232
|
+
}
|
|
233
|
+
function resolveYouStackManifestPath(inputPath) {
|
|
234
|
+
if (inputPath) {
|
|
235
|
+
const resolved = path.resolve(inputPath);
|
|
236
|
+
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
237
|
+
return path.join(resolved, "youstack.json");
|
|
238
|
+
}
|
|
239
|
+
return resolved;
|
|
240
|
+
}
|
|
241
|
+
const candidates = findYouStackManifestCandidates(process.cwd());
|
|
242
|
+
if (candidates.length === 0) {
|
|
243
|
+
throw new Error("No youstack.json found. Create stacks/<slug>/youstack.json (canonical layout), run from a stack directory, or pass --path <manifest-or-dir>.");
|
|
244
|
+
}
|
|
245
|
+
return candidates[0];
|
|
246
|
+
}
|
|
247
|
+
function validateYouStackManifest(value) {
|
|
248
|
+
const errors = [];
|
|
249
|
+
const warnings = [];
|
|
250
|
+
if (!isRecord(value)) {
|
|
251
|
+
return { ok: false, errors: ["manifest must be a JSON object"], warnings };
|
|
252
|
+
}
|
|
253
|
+
expectString(value, "schemaVersion", errors, { exact: "youstack/v1" });
|
|
254
|
+
expectString(value, "kind", errors, { exact: "youstack" });
|
|
255
|
+
const slug = expectString(value, "slug", errors);
|
|
256
|
+
expectString(value, "name", errors);
|
|
257
|
+
const domain = expectString(value, "domain", errors, { optional: true });
|
|
258
|
+
validateOptionalStringArray(value, "aliases", errors);
|
|
259
|
+
validateOptionalStringArray(value, "tags", errors);
|
|
260
|
+
expectString(value, "version", errors);
|
|
261
|
+
const visibility = expectString(value, "visibility", errors);
|
|
262
|
+
if (visibility && !VALID_VISIBILITY.has(visibility)) {
|
|
263
|
+
errors.push(`visibility must be one of: ${Array.from(VALID_VISIBILITY).join(", ")}`);
|
|
264
|
+
}
|
|
265
|
+
if (slug && !isShellSafeIdentifier(slug)) {
|
|
266
|
+
errors.push("slug must use only shell-safe identifier characters: letters, numbers, dot, underscore, dash");
|
|
267
|
+
}
|
|
268
|
+
if (domain && !isSingleLineMetadata(domain)) {
|
|
269
|
+
warnings.push("domain should stay single-line so generated adapter/docs metadata stays readable");
|
|
270
|
+
}
|
|
271
|
+
for (const field of ["aliases", "tags"]) {
|
|
272
|
+
const items = value[field];
|
|
273
|
+
if (!Array.isArray(items))
|
|
274
|
+
continue;
|
|
275
|
+
for (const [index, item] of items.entries()) {
|
|
276
|
+
if (typeof item === "string" && !isSingleLineMetadata(item)) {
|
|
277
|
+
warnings.push(`${field}[${index}] should stay single-line so generated adapter/docs metadata stays readable`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (value.files !== undefined) {
|
|
282
|
+
if (!Array.isArray(value.files)) {
|
|
283
|
+
errors.push("files must be an array");
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
for (const [index, file] of value.files.entries()) {
|
|
287
|
+
if (!isRecord(file)) {
|
|
288
|
+
errors.push(`files[${index}] must be an object`);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const filePath = expectStringField(file, "path", `files[${index}].path`, errors);
|
|
292
|
+
expectStringField(file, "type", `files[${index}].type`, errors);
|
|
293
|
+
if (filePath && !isSafeRelativePath(filePath)) {
|
|
294
|
+
errors.push(`files[${index}].path must be a safe relative path`);
|
|
295
|
+
}
|
|
296
|
+
if (file.checksum !== undefined && typeof file.checksum !== "string") {
|
|
297
|
+
errors.push(`files[${index}].checksum must be a string`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (value.brainScopes !== undefined) {
|
|
303
|
+
if (!Array.isArray(value.brainScopes)) {
|
|
304
|
+
errors.push("brainScopes must be an array");
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
for (const [index, scope] of value.brainScopes.entries()) {
|
|
308
|
+
if (!isRecord(scope)) {
|
|
309
|
+
errors.push(`brainScopes[${index}] must be an object`);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
expectStringField(scope, "scope", `brainScopes[${index}].scope`, errors);
|
|
313
|
+
if (scope.reason === undefined || String(scope.reason || "").trim().length === 0) {
|
|
314
|
+
warnings.push(`brainScopes[${index}] should explain why the scope is needed`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// P19 (2026-06-12): validate requiresScopes entries against known vocabulary.
|
|
320
|
+
if (value.requiresScopes !== undefined) {
|
|
321
|
+
if (!Array.isArray(value.requiresScopes)) {
|
|
322
|
+
errors.push("requiresScopes must be an array");
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
const knownSet = new Set(exports.KNOWN_API_SCOPES);
|
|
326
|
+
for (const [index, scope] of value.requiresScopes.entries()) {
|
|
327
|
+
if (typeof scope !== "string") {
|
|
328
|
+
errors.push(`requiresScopes[${index}] must be a string`);
|
|
329
|
+
}
|
|
330
|
+
else if (!knownSet.has(scope)) {
|
|
331
|
+
warnings.push(`requiresScopes[${index}] "${scope}" is not a known API scope — valid scopes: ${[...exports.KNOWN_API_SCOPES].join(", ")}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (value.capabilities !== undefined) {
|
|
337
|
+
if (!Array.isArray(value.capabilities)) {
|
|
338
|
+
errors.push("capabilities must be an array");
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
const seen = new Set();
|
|
342
|
+
for (const [index, capability] of value.capabilities.entries()) {
|
|
343
|
+
if (!isRecord(capability)) {
|
|
344
|
+
errors.push(`capabilities[${index}] must be an object`);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const id = expectStringField(capability, "id", `capabilities[${index}].id`, errors);
|
|
348
|
+
if (id) {
|
|
349
|
+
if (seen.has(id))
|
|
350
|
+
errors.push(`duplicate capability id: ${id}`);
|
|
351
|
+
seen.add(id);
|
|
352
|
+
if (!isShellSafeIdentifier(id)) {
|
|
353
|
+
errors.push(`capabilities[${index}].id must use only shell-safe identifier characters: letters, numbers, dot, underscore, dash`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (capability.localOnly !== undefined &&
|
|
357
|
+
typeof capability.localOnly !== "boolean") {
|
|
358
|
+
errors.push(`capabilities[${index}].localOnly must be a boolean`);
|
|
359
|
+
}
|
|
360
|
+
if (capability.requiredScopes !== undefined &&
|
|
361
|
+
!Array.isArray(capability.requiredScopes)) {
|
|
362
|
+
errors.push(`capabilities[${index}].requiredScopes must be an array`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (value.adapters !== undefined) {
|
|
368
|
+
if (!isRecord(value.adapters)) {
|
|
369
|
+
errors.push("adapters must be an object keyed by host id");
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
for (const [host, adapter] of Object.entries(value.adapters)) {
|
|
373
|
+
if (!isRecord(adapter)) {
|
|
374
|
+
errors.push(`adapters.${host} must be an object`);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (adapter.files !== undefined) {
|
|
378
|
+
if (!Array.isArray(adapter.files)) {
|
|
379
|
+
errors.push(`adapters.${host}.files must be an array`);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
for (const [index, filePath] of adapter.files.entries()) {
|
|
383
|
+
if (typeof filePath !== "string" || !isSafeRelativePath(filePath)) {
|
|
384
|
+
errors.push(`adapters.${host}.files[${index}] must be a safe relative path`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (isRecord(value.accessPolicy)) {
|
|
393
|
+
if (value.accessPolicy.protectedByDefault !== true) {
|
|
394
|
+
warnings.push("accessPolicy.protectedByDefault should be true for v1 stacks");
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
warnings.push("accessPolicy is missing; defaulting to local/static only");
|
|
399
|
+
}
|
|
400
|
+
if (value.improvement !== undefined) {
|
|
401
|
+
if (!isRecord(value.improvement)) {
|
|
402
|
+
errors.push("improvement must be an object");
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
const mode = value.improvement.mode;
|
|
406
|
+
if (mode !== undefined &&
|
|
407
|
+
mode !== "observe" &&
|
|
408
|
+
mode !== "propose" &&
|
|
409
|
+
mode !== "auto_pr" &&
|
|
410
|
+
mode !== "auto_apply_local") {
|
|
411
|
+
errors.push("improvement.mode must be one of: observe, propose, auto_pr, auto_apply_local");
|
|
412
|
+
}
|
|
413
|
+
validateOptionalStringArray(value.improvement, "signals", errors);
|
|
414
|
+
validateOptionalStringArray(value.improvement, "evals", errors);
|
|
415
|
+
validateOptionalStringArray(value.improvement, "appliesTo", errors);
|
|
416
|
+
validateOptionalStringArray(value.improvement, "approvalRequiredFor", errors);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
warnings.push("improvement policy is missing; self-improvement will not be explicit");
|
|
421
|
+
}
|
|
422
|
+
if (value.update !== undefined) {
|
|
423
|
+
if (!isRecord(value.update)) {
|
|
424
|
+
errors.push("update must be an object");
|
|
425
|
+
}
|
|
426
|
+
else if (value.update.autoApply !== undefined &&
|
|
427
|
+
typeof value.update.autoApply !== "boolean") {
|
|
428
|
+
errors.push("update.autoApply must be a boolean");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
warnings.push("update policy is missing; auto-update behavior will not be explicit");
|
|
433
|
+
}
|
|
434
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
435
|
+
}
|
|
436
|
+
function loadYouStackManifest(inputPath) {
|
|
437
|
+
const manifestPath = resolveYouStackManifestPath(inputPath);
|
|
438
|
+
if (!fs.existsSync(manifestPath)) {
|
|
439
|
+
throw new Error(`YouStack manifest not found: ${manifestPath}`);
|
|
440
|
+
}
|
|
441
|
+
const parsed = parseJsonFile(manifestPath);
|
|
442
|
+
const validation = validateYouStackManifest(parsed);
|
|
443
|
+
return {
|
|
444
|
+
manifest: parsed,
|
|
445
|
+
manifestPath,
|
|
446
|
+
rootDir: path.dirname(manifestPath),
|
|
447
|
+
validation,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function getYouStackReadiness(loaded) {
|
|
451
|
+
if (!loaded) {
|
|
452
|
+
return {
|
|
453
|
+
status: "not_found",
|
|
454
|
+
ready: false,
|
|
455
|
+
reason: "No local YouStack manifest found from the current directory or supplied path.",
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
if (!loaded.validation.ok) {
|
|
459
|
+
return {
|
|
460
|
+
status: "invalid",
|
|
461
|
+
ready: false,
|
|
462
|
+
reason: `Manifest validation failed with ${loaded.validation.errors.length} error${loaded.validation.errors.length === 1 ? "" : "s"}.`,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
status: "ready",
|
|
467
|
+
ready: true,
|
|
468
|
+
reason: "Manifest is present and validation passed.",
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function sha256File(filePath) {
|
|
472
|
+
const hash = crypto.createHash("sha256");
|
|
473
|
+
hash.update(fs.readFileSync(filePath));
|
|
474
|
+
return hash.digest("hex");
|
|
475
|
+
}
|
|
476
|
+
function normalizeChecksum(value) {
|
|
477
|
+
return value.replace(/^sha256:/, "").trim().toLowerCase();
|
|
478
|
+
}
|
|
479
|
+
function getYouStackCapabilities(manifest) {
|
|
480
|
+
return [...BUILT_IN_CAPABILITIES, ...(manifest.capabilities || [])];
|
|
481
|
+
}
|
|
482
|
+
function runYouStackSmoke(loaded) {
|
|
483
|
+
const errors = [...loaded.validation.errors];
|
|
484
|
+
const warnings = [...loaded.validation.warnings];
|
|
485
|
+
const checks = [];
|
|
486
|
+
checks.push(`manifest: ${loaded.manifestPath}`);
|
|
487
|
+
checks.push(`stack: ${loaded.manifest.name} (${loaded.manifest.slug})`);
|
|
488
|
+
if (loaded.manifest.domain)
|
|
489
|
+
checks.push(`domain: ${loaded.manifest.domain}`);
|
|
490
|
+
if (loaded.manifest.tags?.length)
|
|
491
|
+
checks.push(`tags: ${loaded.manifest.tags.join(", ")}`);
|
|
492
|
+
if (loaded.validation.ok)
|
|
493
|
+
checks.push("schema: youstack/v1");
|
|
494
|
+
for (const file of loaded.manifest.files || []) {
|
|
495
|
+
const fullPath = path.join(loaded.rootDir, file.path);
|
|
496
|
+
if (!fs.existsSync(fullPath)) {
|
|
497
|
+
const message = `missing stack file: ${file.path}`;
|
|
498
|
+
if (file.required)
|
|
499
|
+
errors.push(message);
|
|
500
|
+
else
|
|
501
|
+
warnings.push(message);
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
checks.push(`file exists: ${file.path}`);
|
|
505
|
+
if (file.checksum) {
|
|
506
|
+
const actual = sha256File(fullPath);
|
|
507
|
+
const expected = normalizeChecksum(file.checksum);
|
|
508
|
+
if (actual !== expected) {
|
|
509
|
+
errors.push(`checksum mismatch for ${file.path}`);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
checks.push(`checksum ok: ${file.path}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const adapterHosts = Object.keys(loaded.manifest.adapters || {});
|
|
517
|
+
if (adapterHosts.length === 0) {
|
|
518
|
+
warnings.push("no host adapters declared yet");
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
for (const host of adapterHosts) {
|
|
522
|
+
const adapter = loaded.manifest.adapters?.[host];
|
|
523
|
+
checks.push(`adapter declared: ${host}`);
|
|
524
|
+
for (const filePath of adapter?.files || []) {
|
|
525
|
+
if (!isSafeRelativePath(filePath)) {
|
|
526
|
+
errors.push(`adapter ${host} has unsafe path: ${filePath}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const capabilities = getYouStackCapabilities(loaded.manifest);
|
|
532
|
+
checks.push(`capabilities: ${capabilities.length}`);
|
|
533
|
+
if (loaded.manifest.improvement) {
|
|
534
|
+
checks.push(`improvement policy: ${loaded.manifest.improvement.mode || "declared"}`);
|
|
535
|
+
}
|
|
536
|
+
if (loaded.manifest.update) {
|
|
537
|
+
checks.push(`update policy: ${loaded.manifest.update.channel || "declared"}`);
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
ok: errors.length === 0,
|
|
541
|
+
errors,
|
|
542
|
+
warnings,
|
|
543
|
+
checks,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function runYouStackDoctor(loaded) {
|
|
547
|
+
const smoke = runYouStackSmoke(loaded);
|
|
548
|
+
const errors = [...smoke.errors];
|
|
549
|
+
const warnings = [...smoke.warnings];
|
|
550
|
+
const checks = [...smoke.checks];
|
|
551
|
+
const diagnostics = [];
|
|
552
|
+
const recommendations = [];
|
|
553
|
+
const manifest = loaded.manifest;
|
|
554
|
+
const capabilities = getYouStackCapabilities(manifest);
|
|
555
|
+
const files = manifest.files || [];
|
|
556
|
+
const adapters = Object.keys(manifest.adapters || {});
|
|
557
|
+
const manifestBytes = fs.statSync(loaded.manifestPath).size;
|
|
558
|
+
const localCapabilities = capabilities.filter((capability) => capability.localOnly).length;
|
|
559
|
+
const protectedCapabilities = capabilities.length - localCapabilities;
|
|
560
|
+
const fileTypes = new Map();
|
|
561
|
+
const manifestDir = path.dirname(loaded.manifestPath);
|
|
562
|
+
try {
|
|
563
|
+
const gitRoot = (0, child_process_1.execFileSync)("git", ["-C", manifestDir, "rev-parse", "--show-toplevel"], {
|
|
564
|
+
encoding: "utf8",
|
|
565
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
566
|
+
})
|
|
567
|
+
.trim()
|
|
568
|
+
.replace(/\n/g, "");
|
|
569
|
+
if (gitRoot) {
|
|
570
|
+
const porcelain = (0, child_process_1.execFileSync)("git", ["-C", gitRoot, "status", "--porcelain"], {
|
|
571
|
+
encoding: "utf8",
|
|
572
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
573
|
+
}).trim();
|
|
574
|
+
const dirtyCount = porcelain ? porcelain.split("\n").filter(Boolean).length : 0;
|
|
575
|
+
diagnostics.push(`git root: ${gitRoot}`);
|
|
576
|
+
diagnostics.push(dirtyCount > 0 ? `git status: dirty (${dirtyCount} changes)` : "git status: clean");
|
|
577
|
+
if (dirtyCount > 0) {
|
|
578
|
+
warnings.push("git working tree has uncommitted changes; stack adapters and smoke results may not be reproducible");
|
|
579
|
+
}
|
|
580
|
+
let upstream = "";
|
|
581
|
+
try {
|
|
582
|
+
upstream = (0, child_process_1.execFileSync)("git", ["-C", gitRoot, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
|
|
583
|
+
encoding: "utf8",
|
|
584
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
585
|
+
}).trim();
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
upstream = "";
|
|
589
|
+
}
|
|
590
|
+
if (upstream) {
|
|
591
|
+
try {
|
|
592
|
+
const counts = (0, child_process_1.execFileSync)("git", ["-C", gitRoot, "rev-list", "--left-right", "--count", `HEAD...${upstream}`], {
|
|
593
|
+
encoding: "utf8",
|
|
594
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
595
|
+
}).trim();
|
|
596
|
+
const [aheadRaw, behindRaw] = counts.split(/\s+/);
|
|
597
|
+
const ahead = Number(aheadRaw || 0);
|
|
598
|
+
const behind = Number(behindRaw || 0);
|
|
599
|
+
diagnostics.push(`git upstream: ${upstream} (ahead ${ahead}, behind ${behind})`);
|
|
600
|
+
if (behind > 0) {
|
|
601
|
+
warnings.push(`git upstream is behind by ${behind} commits; pull before publishing stack changes`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
diagnostics.push(`git upstream: ${upstream}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
diagnostics.push("git upstream: none");
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
// Not a git repo (or git unavailable). Ignore.
|
|
615
|
+
}
|
|
616
|
+
for (const file of files) {
|
|
617
|
+
fileTypes.set(file.type, (fileTypes.get(file.type) || 0) + 1);
|
|
618
|
+
}
|
|
619
|
+
const canonicalLayout = isCanonicalYouStackManifestPath(loaded.manifestPath);
|
|
620
|
+
const stackDirName = path.basename(path.dirname(loaded.manifestPath));
|
|
621
|
+
const slugHint = typeof manifest.slug === "string" && manifest.slug.trim().length > 0
|
|
622
|
+
? manifest.slug
|
|
623
|
+
: "<slug>";
|
|
624
|
+
diagnostics.push(`layout: ${canonicalLayout ? "canonical (stacks/<slug>/youstack.json)" : "legacy"}`);
|
|
625
|
+
if (!canonicalLayout) {
|
|
626
|
+
warnings.push(`stack manifest found outside the canonical layout; move it to stacks/${slugHint}/youstack.json so cli discovery and you.md repo sync agree (legacy youstack.json, .you/, and youstacks/ locations still load)`);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
// P20 shadowing: a canonical manifest wins discovery — surface any
|
|
630
|
+
// legacy manifests it silently shadows so stale copies get cleaned up.
|
|
631
|
+
const shadowedLegacy = findYouStackManifestCandidates(path.dirname(loaded.manifestPath))
|
|
632
|
+
.filter((candidate) => path.resolve(candidate) !== path.resolve(loaded.manifestPath) &&
|
|
633
|
+
!isCanonicalYouStackManifestPath(candidate));
|
|
634
|
+
if (shadowedLegacy.length > 0) {
|
|
635
|
+
warnings.push(`legacy stack manifest${shadowedLegacy.length === 1 ? "" : "s"} shadowed by the canonical layout: ${shadowedLegacy.join(", ")} (canonical ${loaded.manifestPath} wins discovery; migrate or remove the legacy cop${shadowedLegacy.length === 1 ? "y" : "ies"})`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (canonicalLayout && slugHint !== "<slug>" && stackDirName !== manifest.slug) {
|
|
639
|
+
warnings.push(`stack folder name "${stackDirName}" does not match manifest slug "${manifest.slug}"; rename the folder to stacks/${manifest.slug}/ so repo stack derivation uses the right slug`);
|
|
640
|
+
}
|
|
641
|
+
if (!loaded.validation.ok) {
|
|
642
|
+
warnings.push(`manifest is invalid (${loaded.validation.errors.length} error${loaded.validation.errors.length === 1 ? "" : "s"}); fix the missing or invalid fields reported above, then rerun youmd stack doctor`);
|
|
643
|
+
}
|
|
644
|
+
diagnostics.push(`manifest bytes: ${manifestBytes}`);
|
|
645
|
+
diagnostics.push(`files: ${files.length}`);
|
|
646
|
+
diagnostics.push(`file types: ${Array.from(fileTypes.entries()).map(([type, count]) => `${type}=${count}`).join(", ") || "none"}`);
|
|
647
|
+
diagnostics.push(`capabilities: ${capabilities.length} (${localCapabilities} local, ${protectedCapabilities} protected)`);
|
|
648
|
+
diagnostics.push(`adapters: ${adapters.join(", ") || "none"}`);
|
|
649
|
+
diagnostics.push(`brain scopes: ${manifest.brainScopes?.length || 0}`);
|
|
650
|
+
// P19: surface requiresScopes count and any unknown scope names.
|
|
651
|
+
if (manifest.requiresScopes && manifest.requiresScopes.length > 0) {
|
|
652
|
+
const knownSet = new Set(exports.KNOWN_API_SCOPES);
|
|
653
|
+
const unknownScopes = manifest.requiresScopes.filter((s) => !knownSet.has(s));
|
|
654
|
+
diagnostics.push(`requires scopes: ${manifest.requiresScopes.join(", ")}`);
|
|
655
|
+
for (const unknown of unknownScopes) {
|
|
656
|
+
warnings.push(`requiresScopes contains unknown scope "${unknown}" — valid: ${[...exports.KNOWN_API_SCOPES].join(", ")}`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (manifestBytes > 64 * 1024) {
|
|
660
|
+
warnings.push("manifest is over 64KB; move long docs/prompts into files and keep the manifest routable");
|
|
661
|
+
}
|
|
662
|
+
if (!isShellSafeIdentifier(manifest.slug)) {
|
|
663
|
+
warnings.push("stack slug is not shell-safe; adapter paths and generated host files should use letters, numbers, dot, underscore, and dash only");
|
|
664
|
+
}
|
|
665
|
+
if (files.length > 50) {
|
|
666
|
+
warnings.push("stack declares more than 50 files; consider splitting into named domain stacks");
|
|
667
|
+
}
|
|
668
|
+
if (capabilities.length > 40) {
|
|
669
|
+
warnings.push("stack exposes more than 40 capabilities; route quality may degrade without narrower intents");
|
|
670
|
+
}
|
|
671
|
+
if (adapters.length === 0) {
|
|
672
|
+
recommendations.push("Add host adapters for Claude Code, Codex, and Cursor so the stack is useful after one install.");
|
|
673
|
+
}
|
|
674
|
+
// L18: validate workflow action types.
|
|
675
|
+
const VALID_WORKFLOW_ACTIONS = new Set(["run_skill", "report_skill_outcome"]);
|
|
676
|
+
if (manifest.workflows) {
|
|
677
|
+
for (const wf of manifest.workflows) {
|
|
678
|
+
if (!wf.id) {
|
|
679
|
+
warnings.push("workflow entry is missing an id field");
|
|
680
|
+
}
|
|
681
|
+
if (!wf.schedule) {
|
|
682
|
+
warnings.push(`workflow ${wf.id || "?"}: missing schedule (cron string required)`);
|
|
683
|
+
}
|
|
684
|
+
if (!VALID_WORKFLOW_ACTIONS.has(wf.action)) {
|
|
685
|
+
warnings.push(`workflow ${wf.id || "?"}: unknown action type "${wf.action}" — valid types are: ${[...VALID_WORKFLOW_ACTIONS].join(", ")}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
if (!files.some((file) => file.type === "test")) {
|
|
690
|
+
recommendations.push("Add a read-only smoke test file so agents can verify the stack before using it.");
|
|
691
|
+
}
|
|
692
|
+
if (!manifest.improvement?.evals?.some((item) => item.includes("youmd stack smoke"))) {
|
|
693
|
+
recommendations.push("Include `youmd stack smoke` in improvement.evals so self-improvement stays gated.");
|
|
694
|
+
}
|
|
695
|
+
if (!manifest.update?.check) {
|
|
696
|
+
recommendations.push("Declare update.check so host agents know exactly how to refresh the stack.");
|
|
697
|
+
}
|
|
698
|
+
else if (manifest.update.autoApply === true &&
|
|
699
|
+
!manifest.update.check.includes("youmd-auto-upgrade")) {
|
|
700
|
+
recommendations.push("Auto-applying stacks should run `~/.youmd/bin/youmd-auto-upgrade --quiet` before stack updates.");
|
|
701
|
+
}
|
|
702
|
+
for (const capability of capabilities) {
|
|
703
|
+
const protectedMutation = !capability.localOnly &&
|
|
704
|
+
capability.mutationPolicy &&
|
|
705
|
+
capability.mutationPolicy !== "read_only";
|
|
706
|
+
if (protectedMutation && (!capability.requiredScopes || capability.requiredScopes.length === 0)) {
|
|
707
|
+
warnings.push(`protected capability ${capability.id} mutates state without requiredScopes`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (manifest.visibility === "public-open") {
|
|
711
|
+
const publicReadiness = manifest.sharing?.publicReadiness;
|
|
712
|
+
if (!Array.isArray(publicReadiness) || publicReadiness.length === 0) {
|
|
713
|
+
warnings.push("public-open stacks should declare sharing.publicReadiness checks");
|
|
714
|
+
}
|
|
715
|
+
if (!capabilities.some((capability) => capability.id.includes("public-readiness"))) {
|
|
716
|
+
recommendations.push("Add a public-readiness capability that redacts secrets, private memories, and proprietary prompts before publishing.");
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (recommendations.length === 0) {
|
|
720
|
+
recommendations.push("No structural recommendations. Keep watching route misses, user corrections, eval failures, and reference-intelligence tasks.");
|
|
721
|
+
}
|
|
722
|
+
// Empirical Claude Code discovery gate: verify the SKILL.md files this
|
|
723
|
+
// stack would emit for the claude-code host. Only meaningful once the
|
|
724
|
+
// manifest itself is valid.
|
|
725
|
+
let discovery = [];
|
|
726
|
+
if (loaded.validation.ok) {
|
|
727
|
+
try {
|
|
728
|
+
discovery = verifyYouStackClaudeDiscovery(loaded).checks;
|
|
729
|
+
for (const check of discovery) {
|
|
730
|
+
if (check.ok) {
|
|
731
|
+
checks.push(`claude discovery ok: ${check.path}`);
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
for (const problem of check.problems) {
|
|
735
|
+
errors.push(`claude discovery failed for ${check.path}: ${problem}`);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
catch (error) {
|
|
741
|
+
errors.push(`claude discovery check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
diagnostics.push("claude discovery: skipped (manifest invalid)");
|
|
746
|
+
}
|
|
747
|
+
// L12 safety guard: run for all manifests; missing contract → T0 with warning.
|
|
748
|
+
// When a safety contract is explicitly declared, violations are hard errors.
|
|
749
|
+
// When no contract is present (defaulting to T0), inferred-capability mismatches
|
|
750
|
+
// are surfaced as warnings so existing manifests without a safety section are
|
|
751
|
+
// not retroactively broken.
|
|
752
|
+
let guard;
|
|
753
|
+
try {
|
|
754
|
+
guard = (0, stackSafety_1.runStackGuard)(manifest, loaded.manifestPath);
|
|
755
|
+
const hasExplicitContract = (0, stackSafety_1.parseSafetyContract)(manifest) !== null;
|
|
756
|
+
for (const warning of guard.warnings) {
|
|
757
|
+
warnings.push(`guard: ${warning}`);
|
|
758
|
+
}
|
|
759
|
+
for (const violation of guard.contractViolations) {
|
|
760
|
+
// Contract violations are always hard errors regardless of whether the
|
|
761
|
+
// contract was explicit (the manifest declared a safety section that is
|
|
762
|
+
// internally inconsistent).
|
|
763
|
+
errors.push(`guard contract violation: ${violation.reason}`);
|
|
764
|
+
}
|
|
765
|
+
for (const check of guard.checks) {
|
|
766
|
+
if (check.status === "VIOLATION") {
|
|
767
|
+
if (hasExplicitContract) {
|
|
768
|
+
// Explicit contract: capability exceeds the declared tier → hard error
|
|
769
|
+
errors.push(`guard ${check.capability}: ${check.message}`);
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
// Implicit T0 default: inferred violations are advisory warnings only
|
|
773
|
+
warnings.push(`guard (advisory): ${check.capability}: ${check.message} — declare a safety section to enforce this`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
diagnostics.push(`guard ${check.capability}: ${check.message}`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (guard.ok || !hasExplicitContract) {
|
|
781
|
+
checks.push(`safety guard: ${guard.tier} — all capability checks passed`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
catch (error) {
|
|
785
|
+
warnings.push(`guard check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
ok: errors.length === 0,
|
|
789
|
+
errors,
|
|
790
|
+
warnings,
|
|
791
|
+
checks,
|
|
792
|
+
diagnostics,
|
|
793
|
+
recommendations,
|
|
794
|
+
discovery,
|
|
795
|
+
guard,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
function capabilityText(capability) {
|
|
799
|
+
return [
|
|
800
|
+
capability.id,
|
|
801
|
+
capability.intent,
|
|
802
|
+
capability.workflow,
|
|
803
|
+
capability.skill,
|
|
804
|
+
capability.mcpTool,
|
|
805
|
+
capability.apiEndpoint,
|
|
806
|
+
...(capability.requiredScopes || []),
|
|
807
|
+
]
|
|
808
|
+
.filter(Boolean)
|
|
809
|
+
.join(" ")
|
|
810
|
+
.toLowerCase();
|
|
811
|
+
}
|
|
812
|
+
function scoreCapability(request, capability) {
|
|
813
|
+
const query = request.toLowerCase().trim();
|
|
814
|
+
if (!query)
|
|
815
|
+
return 0;
|
|
816
|
+
const haystack = capabilityText(capability);
|
|
817
|
+
let score = 0;
|
|
818
|
+
if (haystack.includes(query))
|
|
819
|
+
score += 12;
|
|
820
|
+
for (const raw of query.split(/[^a-z0-9_.:-]+/).filter(Boolean)) {
|
|
821
|
+
if (raw.length < 2)
|
|
822
|
+
continue;
|
|
823
|
+
if (haystack.includes(raw))
|
|
824
|
+
score += raw.length > 4 ? 3 : 1;
|
|
825
|
+
}
|
|
826
|
+
if (capability.localOnly)
|
|
827
|
+
score += 1;
|
|
828
|
+
if (capability.mutationPolicy === "read_only")
|
|
829
|
+
score += 1;
|
|
830
|
+
if (/\b(doctor|diagnose|diagnostic|health|bloat|leak|resource|drift|stale)\b/.test(query) && /diagnos|health|bloat|drift|hygiene|readiness/.test(haystack)) {
|
|
831
|
+
score += 8;
|
|
832
|
+
}
|
|
833
|
+
return score;
|
|
834
|
+
}
|
|
835
|
+
function routeYouStackRequest(manifest, request) {
|
|
836
|
+
const scored = getYouStackCapabilities(manifest)
|
|
837
|
+
.map((capability) => ({ capability, score: scoreCapability(request, capability) }))
|
|
838
|
+
.sort((a, b) => b.score - a.score || a.capability.id.localeCompare(b.capability.id));
|
|
839
|
+
const best = scored[0] || {
|
|
840
|
+
capability: BUILT_IN_CAPABILITIES[0],
|
|
841
|
+
score: 0,
|
|
842
|
+
};
|
|
843
|
+
const reasons = best.score > 0
|
|
844
|
+
? [`Matched request terms against capability ${best.capability.id}.`]
|
|
845
|
+
: ["No strong match found; defaulted to safe manifest inspection."];
|
|
846
|
+
return {
|
|
847
|
+
request,
|
|
848
|
+
capability: best.capability,
|
|
849
|
+
score: best.score,
|
|
850
|
+
reasons,
|
|
851
|
+
alternatives: scored.slice(1, 4),
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Runtime enforcement hook (L12).
|
|
856
|
+
*
|
|
857
|
+
* Maps a mutation policy to a capability key, then checks the safety contract.
|
|
858
|
+
* Returns a refusal message if the requested action exceeds the declared tier,
|
|
859
|
+
* or null if the action is allowed.
|
|
860
|
+
*
|
|
861
|
+
* Usage: call this before any write action in stack routing flows.
|
|
862
|
+
*
|
|
863
|
+
* Example refusal: "blocked: stack is T1 (suggest-only) — this action needs T2"
|
|
864
|
+
*/
|
|
865
|
+
function checkTierAllows(manifest, capabilityKey) {
|
|
866
|
+
const contract = (0, stackSafety_1.parseSafetyContract)(manifest);
|
|
867
|
+
if (!contract) {
|
|
868
|
+
// No contract → treat as T0; deny write capabilities
|
|
869
|
+
if (capabilityKey === "fs_write" || capabilityKey === "shell" || capabilityKey === "auto_pr") {
|
|
870
|
+
return (`blocked: no safety contract declared (defaulting to T0, read-only). ` +
|
|
871
|
+
`Add a safety section to youstack.json to grant ${capabilityKey}.`);
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
if (!(0, stackSafety_1.tierAllows)(contract, capabilityKey)) {
|
|
876
|
+
return (0, stackSafety_1.tierRefusalMessage)(contract, capabilityKey);
|
|
877
|
+
}
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
function normalizeYouStackHost(host) {
|
|
881
|
+
const value = host.trim().toLowerCase();
|
|
882
|
+
if (value === "claude")
|
|
883
|
+
return "claude-code";
|
|
884
|
+
if (value === "claude-code")
|
|
885
|
+
return "claude-code";
|
|
886
|
+
if (value === "codex")
|
|
887
|
+
return "codex";
|
|
888
|
+
if (value === "cursor")
|
|
889
|
+
return "cursor";
|
|
890
|
+
return value;
|
|
891
|
+
}
|
|
892
|
+
/** Default link path for one host — derived from the engine adapter config. */
|
|
893
|
+
function defaultAdapterPath(host, slug) {
|
|
894
|
+
return (0, host_link_1.hostLinkRelativePath)((0, host_link_1.youStackHostAdapterConfig)(normalizeYouStackHost(host)), slug);
|
|
895
|
+
}
|
|
896
|
+
function declaredAdapterPaths(manifest, host) {
|
|
897
|
+
const normalized = normalizeYouStackHost(host);
|
|
898
|
+
const declared = manifest.adapters?.[normalized]?.files || manifest.adapters?.[host]?.files;
|
|
899
|
+
return declared && declared.length > 0 ? declared : undefined;
|
|
900
|
+
}
|
|
901
|
+
/** The stack rendered as one linkable skill unit for the host-link engine. */
|
|
902
|
+
function youStackSkillUnit(manifest) {
|
|
903
|
+
return {
|
|
904
|
+
name: manifest.slug,
|
|
905
|
+
description: manifest.description || `Use the ${manifest.name} YouStack safely.`,
|
|
906
|
+
content: generateYouStackAdapterBody(manifest),
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Render the stack adapter content for a host. Frontmatter shape is decided
|
|
911
|
+
* by the engine's per-host adapter config, not by host conditionals here.
|
|
912
|
+
*/
|
|
913
|
+
function generateYouStackAdapterContent(manifest, host) {
|
|
914
|
+
const config = (0, host_link_1.youStackHostAdapterConfig)(normalizeYouStackHost(host));
|
|
915
|
+
const entries = (0, host_link_1.buildHostLinkPlan)([youStackSkillUnit(manifest)], config);
|
|
916
|
+
return entries[0]?.content || "";
|
|
917
|
+
}
|
|
918
|
+
function generateYouStackAdapterBody(manifest) {
|
|
919
|
+
const capabilities = getYouStackCapabilities(manifest);
|
|
920
|
+
const brainScopes = manifest.brainScopes || [];
|
|
921
|
+
const lines = [];
|
|
922
|
+
lines.push(`# ${manifest.name} YouStack`);
|
|
923
|
+
lines.push("");
|
|
924
|
+
lines.push("This file is generated from `youstack.json`.");
|
|
925
|
+
lines.push("");
|
|
926
|
+
lines.push("## Stack Identity");
|
|
927
|
+
lines.push("");
|
|
928
|
+
lines.push(`- Name: ${manifest.name}`);
|
|
929
|
+
lines.push(`- Slug: ${manifest.slug}`);
|
|
930
|
+
if (manifest.domain)
|
|
931
|
+
lines.push(`- Domain: ${manifest.domain}`);
|
|
932
|
+
if (manifest.aliases?.length)
|
|
933
|
+
lines.push(`- Aliases: ${manifest.aliases.join(", ")}`);
|
|
934
|
+
if (manifest.tags?.length)
|
|
935
|
+
lines.push(`- Tags: ${manifest.tags.join(", ")}`);
|
|
936
|
+
lines.push("");
|
|
937
|
+
lines.push("## Startup");
|
|
938
|
+
lines.push("");
|
|
939
|
+
lines.push("1. Run `~/.youmd/bin/youmd-auto-upgrade --quiet || true` when the helper exists.");
|
|
940
|
+
lines.push("2. Run `youmd stack smoke` from the stack or project root before relying on this stack.");
|
|
941
|
+
lines.push("3. Read local project instructions and project-context files before acting.");
|
|
942
|
+
lines.push("4. Use local/static stack files first.");
|
|
943
|
+
lines.push("5. Use shared You.md API/MCP only for protected brain retrieval, sync, tokens, connected tools, or server-side actions.");
|
|
944
|
+
lines.push("6. Protected reads may return readiness states such as auth_required, unavailable, or ready; if richer retrieval is unavailable, fall back to local stack files, project-context files, and public identity first.");
|
|
945
|
+
lines.push("7. Do not mutate brain data, connected tools, visibility, or repo files until the user approves the exact action.");
|
|
946
|
+
lines.push("");
|
|
947
|
+
lines.push("## Capabilities");
|
|
948
|
+
lines.push("");
|
|
949
|
+
for (const capability of capabilities) {
|
|
950
|
+
const mode = capability.localOnly ? "local" : "protected";
|
|
951
|
+
lines.push(`- ${capability.id} (${mode}, ${capability.mutationPolicy || "unspecified"}): ${capability.intent || "No intent declared."}`);
|
|
952
|
+
}
|
|
953
|
+
lines.push("");
|
|
954
|
+
lines.push("## Brain Scopes");
|
|
955
|
+
lines.push("");
|
|
956
|
+
if (brainScopes.length === 0) {
|
|
957
|
+
lines.push("- None declared.");
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
for (const scope of brainScopes) {
|
|
961
|
+
const required = scope.required ? "required" : "optional";
|
|
962
|
+
lines.push(`- ${scope.scope} (${required}): ${scope.reason || "No reason declared."}`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
lines.push("");
|
|
966
|
+
lines.push("## Self-Improvement");
|
|
967
|
+
lines.push("");
|
|
968
|
+
if (manifest.improvement) {
|
|
969
|
+
lines.push(`- Mode: ${manifest.improvement.mode || "observe"}`);
|
|
970
|
+
if (manifest.improvement.cadence)
|
|
971
|
+
lines.push(`- Cadence: ${manifest.improvement.cadence}`);
|
|
972
|
+
if (manifest.improvement.appliesTo?.length)
|
|
973
|
+
lines.push(`- Applies to: ${manifest.improvement.appliesTo.join(", ")}`);
|
|
974
|
+
if (manifest.improvement.signals?.length)
|
|
975
|
+
lines.push(`- Signals: ${manifest.improvement.signals.join(", ")}`);
|
|
976
|
+
if (manifest.improvement.evals?.length)
|
|
977
|
+
lines.push(`- Evals: ${manifest.improvement.evals.join(", ")}`);
|
|
978
|
+
if (manifest.improvement.approvalRequiredFor?.length) {
|
|
979
|
+
lines.push(`- Approval required for: ${manifest.improvement.approvalRequiredFor.join(", ")}`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
else {
|
|
983
|
+
lines.push("- No explicit improvement policy declared. Treat improvements as propose-only until the user chooses a policy.");
|
|
984
|
+
}
|
|
985
|
+
if (manifest.update) {
|
|
986
|
+
lines.push(`- Update channel: ${manifest.update.channel || "manual"}`);
|
|
987
|
+
if (manifest.update.check)
|
|
988
|
+
lines.push(`- Update check: ${manifest.update.check}`);
|
|
989
|
+
if (manifest.update.source)
|
|
990
|
+
lines.push(`- Update source: ${manifest.update.source}`);
|
|
991
|
+
lines.push(`- Auto-apply local updates: ${manifest.update.autoApply === true ? "yes" : "no"}`);
|
|
992
|
+
}
|
|
993
|
+
lines.push("- When improving this stack, update stack files, docs, evals/tests, and adapter outputs together, then run `youmd stack smoke`.");
|
|
994
|
+
lines.push("- Use the bundled `youstack-maintainer` skill for stack organization, visibility review, public-readiness checks, and safe self-updates.");
|
|
995
|
+
lines.push("- Never auto-write protected brain data, private context, connected tools, or remote repos unless the manifest policy and user approval both allow it.");
|
|
996
|
+
lines.push("");
|
|
997
|
+
lines.push("## Local Commands");
|
|
998
|
+
lines.push("");
|
|
999
|
+
lines.push("```bash");
|
|
1000
|
+
lines.push("youmd stack inspect");
|
|
1001
|
+
lines.push("youmd stack doctor");
|
|
1002
|
+
lines.push("youmd stack smoke");
|
|
1003
|
+
lines.push("youmd stack capabilities");
|
|
1004
|
+
lines.push("youmd stack route \"<request>\"");
|
|
1005
|
+
lines.push("```");
|
|
1006
|
+
lines.push("");
|
|
1007
|
+
return lines.join("\n");
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Build the per-host link plans (paths + rendered content) for a stack
|
|
1011
|
+
* without touching the filesystem. All hosts go through the one engine;
|
|
1012
|
+
* manifest-declared adapter paths still override the default layout.
|
|
1013
|
+
*/
|
|
1014
|
+
function planYouStackAdapterLinks(loaded, hosts) {
|
|
1015
|
+
const hostList = hosts && hosts.length > 0
|
|
1016
|
+
? hosts.map(normalizeYouStackHost)
|
|
1017
|
+
: Object.keys(loaded.manifest.adapters || {}).map(normalizeYouStackHost);
|
|
1018
|
+
const resolvedHosts = hostList.length > 0 ? [...new Set(hostList)] : ["claude-code", "codex", "cursor"];
|
|
1019
|
+
const unit = youStackSkillUnit(loaded.manifest);
|
|
1020
|
+
return resolvedHosts.flatMap((host) => (0, host_link_1.buildHostLinkPlan)([unit], (0, host_link_1.youStackHostAdapterConfig)(host), {
|
|
1021
|
+
explicitRelativePaths: declaredAdapterPaths(loaded.manifest, host),
|
|
1022
|
+
}));
|
|
1023
|
+
}
|
|
1024
|
+
function linkYouStackAdapters(loaded, options = {}) {
|
|
1025
|
+
const targetDir = path.resolve(options.targetDir || process.cwd());
|
|
1026
|
+
const plan = planYouStackAdapterLinks(loaded, options.hosts);
|
|
1027
|
+
return (0, host_link_1.writeHostLinkPlan)(plan, targetDir, { dryRun: options.dryRun }).map((result) => ({
|
|
1028
|
+
host: result.host,
|
|
1029
|
+
targetPath: result.targetPath,
|
|
1030
|
+
wrote: result.wrote,
|
|
1031
|
+
content: result.content,
|
|
1032
|
+
}));
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Empirical Claude Code discovery release gate (exported for CI use):
|
|
1036
|
+
* verify that every SKILL.md this stack would emit for the claude-code
|
|
1037
|
+
* host parses, carries name + description, name matches its directory,
|
|
1038
|
+
* and has a non-empty body.
|
|
1039
|
+
*/
|
|
1040
|
+
function verifyYouStackClaudeDiscovery(loaded) {
|
|
1041
|
+
return (0, host_link_1.verifySkillDiscovery)(planYouStackAdapterLinks(loaded, ["claude-code"]));
|
|
1042
|
+
}
|
|
1043
|
+
// Re-export the guard types and runner so callers only need one import.
|
|
1044
|
+
var stackSafety_2 = require("./stackSafety");
|
|
1045
|
+
Object.defineProperty(exports, "runStackGuard", { enumerable: true, get: function () { return stackSafety_2.runStackGuard; } });
|
|
1046
|
+
Object.defineProperty(exports, "parseSafetyContract", { enumerable: true, get: function () { return stackSafety_2.parseSafetyContract; } });
|
|
1047
|
+
Object.defineProperty(exports, "tierAllows", { enumerable: true, get: function () { return stackSafety_2.tierAllows; } });
|
|
1048
|
+
Object.defineProperty(exports, "tierRefusalMessage", { enumerable: true, get: function () { return stackSafety_2.tierRefusalMessage; } });
|
|
1049
|
+
//# sourceMappingURL=youstack.js.map
|