@rasensio/aidlc 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/cli.js +2 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/add-action.d.ts.map +1 -1
  4. package/dist/commands/add-action.js +2 -3
  5. package/dist/commands/add-action.js.map +1 -1
  6. package/dist/commands/add-skill.d.ts +28 -0
  7. package/dist/commands/add-skill.d.ts.map +1 -0
  8. package/dist/commands/add-skill.js +315 -0
  9. package/dist/commands/add-skill.js.map +1 -0
  10. package/dist/commands/add.d.ts +25 -0
  11. package/dist/commands/add.d.ts.map +1 -0
  12. package/dist/commands/add.js +29 -0
  13. package/dist/commands/add.js.map +1 -0
  14. package/dist/commands/cost.d.ts +2 -0
  15. package/dist/commands/cost.d.ts.map +1 -1
  16. package/dist/commands/cost.js +16 -1
  17. package/dist/commands/cost.js.map +1 -1
  18. package/dist/commands/init.d.ts.map +1 -1
  19. package/dist/commands/init.js +1 -10
  20. package/dist/commands/init.js.map +1 -1
  21. package/dist/commands/update.d.ts.map +1 -1
  22. package/dist/commands/update.js +1 -9
  23. package/dist/commands/update.js.map +1 -1
  24. package/dist/compile/adapters/claude-code.d.ts.map +1 -1
  25. package/dist/compile/adapters/claude-code.js +3 -24
  26. package/dist/compile/adapters/claude-code.js.map +1 -1
  27. package/dist/compile/adapters/codex.d.ts.map +1 -1
  28. package/dist/compile/adapters/codex.js +3 -2
  29. package/dist/compile/adapters/codex.js.map +1 -1
  30. package/dist/compile/adapters/cursor.d.ts.map +1 -1
  31. package/dist/compile/adapters/cursor.js +4 -2
  32. package/dist/compile/adapters/cursor.js.map +1 -1
  33. package/dist/compile/adapters/kiro.d.ts.map +1 -1
  34. package/dist/compile/adapters/kiro.js +6 -10
  35. package/dist/compile/adapters/kiro.js.map +1 -1
  36. package/dist/compile/adapters/windsurf.d.ts.map +1 -1
  37. package/dist/compile/adapters/windsurf.js +9 -11
  38. package/dist/compile/adapters/windsurf.js.map +1 -1
  39. package/dist/compile/content-root.d.ts +22 -0
  40. package/dist/compile/content-root.d.ts.map +1 -0
  41. package/dist/compile/content-root.js +28 -0
  42. package/dist/compile/content-root.js.map +1 -0
  43. package/dist/compile/limits.d.ts +20 -0
  44. package/dist/compile/limits.d.ts.map +1 -0
  45. package/dist/compile/limits.js +20 -0
  46. package/dist/compile/limits.js.map +1 -0
  47. package/dist/compile/naming.d.ts +92 -0
  48. package/dist/compile/naming.d.ts.map +1 -0
  49. package/dist/compile/naming.js +105 -0
  50. package/dist/compile/naming.js.map +1 -0
  51. package/dist/cost/providers/claude-code.d.ts +2 -0
  52. package/dist/cost/providers/claude-code.d.ts.map +1 -1
  53. package/dist/cost/providers/claude-code.js +34 -12
  54. package/dist/cost/providers/claude-code.js.map +1 -1
  55. package/dist/cost/providers/kiro.d.ts.map +1 -1
  56. package/dist/cost/providers/kiro.js +23 -10
  57. package/dist/cost/providers/kiro.js.map +1 -1
  58. package/dist/cost/providers/provider.d.ts +9 -4
  59. package/dist/cost/providers/provider.d.ts.map +1 -1
  60. package/dist/cost/providers/provider.js.map +1 -1
  61. package/dist/cost/recorder.d.ts +27 -3
  62. package/dist/cost/recorder.d.ts.map +1 -1
  63. package/dist/cost/recorder.js +116 -11
  64. package/dist/cost/recorder.js.map +1 -1
  65. package/dist/cost/sync.js +2 -2
  66. package/dist/cost/sync.js.map +1 -1
  67. package/dist/cost/types.d.ts +20 -0
  68. package/dist/cost/types.d.ts.map +1 -1
  69. package/dist/cost/types.js +2 -0
  70. package/dist/cost/types.js.map +1 -1
  71. package/dist/doctor/migrations/index.d.ts.map +1 -1
  72. package/dist/doctor/migrations/index.js +2 -0
  73. package/dist/doctor/migrations/index.js.map +1 -1
  74. package/dist/doctor/migrations/prune-capture-log.d.ts +11 -0
  75. package/dist/doctor/migrations/prune-capture-log.d.ts.map +1 -0
  76. package/dist/doctor/migrations/prune-capture-log.js +31 -0
  77. package/dist/doctor/migrations/prune-capture-log.js.map +1 -0
  78. package/dist/operational/discover.d.ts +89 -0
  79. package/dist/operational/discover.d.ts.map +1 -0
  80. package/dist/operational/discover.js +132 -0
  81. package/dist/operational/discover.js.map +1 -0
  82. package/dist/operational/emit.d.ts +93 -0
  83. package/dist/operational/emit.d.ts.map +1 -0
  84. package/dist/operational/emit.js +227 -0
  85. package/dist/operational/emit.js.map +1 -0
  86. package/dist/operational/knowledge.d.ts +64 -0
  87. package/dist/operational/knowledge.d.ts.map +1 -0
  88. package/dist/operational/knowledge.js +144 -0
  89. package/dist/operational/knowledge.js.map +1 -0
  90. package/dist/operational/packagers.d.ts +67 -0
  91. package/dist/operational/packagers.d.ts.map +1 -0
  92. package/dist/operational/packagers.js +252 -0
  93. package/dist/operational/packagers.js.map +1 -0
  94. package/dist/operational/redact.d.ts +49 -0
  95. package/dist/operational/redact.d.ts.map +1 -0
  96. package/dist/operational/redact.js +135 -0
  97. package/dist/operational/redact.js.map +1 -0
  98. package/dist/operational/schema.d.ts +80 -0
  99. package/dist/operational/schema.d.ts.map +1 -0
  100. package/dist/operational/schema.js +205 -0
  101. package/dist/operational/schema.js.map +1 -0
  102. package/dist/operational/verify.d.ts +100 -0
  103. package/dist/operational/verify.d.ts.map +1 -0
  104. package/dist/operational/verify.js +175 -0
  105. package/dist/operational/verify.js.map +1 -0
  106. package/package.json +2 -2
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Knowledge-graph registration for operational skills.
3
+ *
4
+ * Registers each generated skill so `aidlc knowledge query <name>` finds it and a
5
+ * developer returning months later does not write a second skill for the same job
6
+ * (US-6, AC-28…AC-30, AC-49).
7
+ *
8
+ * Two properties are load-bearing and neither is free:
9
+ *
10
+ * - **The entity type is not gate-counted.** `custom:operational-skill` rather than
11
+ * `module`, because the ideation seeding gate counts `module`, `convention`, and
12
+ * `depends-on` (`knowledge/gate-criterion.ts`). Registering skills as modules
13
+ * would let generated files satisfy a lifecycle gate meant to measure real
14
+ * architecture.
15
+ * - **The id is namespaced and collisions are refused.** `addEntity` merges into any
16
+ * pre-existing entity with the same id, and `mergeEntities` never overrides
17
+ * `type` — so an unnamespaced id colliding with an existing `module` would stay
18
+ * gate-counted *and* clobber an architecture fact. Registration therefore checks
19
+ * the resolved type first and refuses rather than merging.
20
+ *
21
+ * Registration is best-effort: failure warns and generation still succeeds (AC-30).
22
+ *
23
+ * Requirements: AC-28, AC-29, AC-30, AC-49
24
+ *
25
+ * @module
26
+ */
27
+ import { knowledgeExists, openKnowledge } from '../knowledge/engine.js';
28
+ /** Entity type for operational skills — deliberately not gate-counted (AC-28). */
29
+ export const OPERATIONAL_ENTITY_TYPE = 'custom:operational-skill';
30
+ /** Id prefix keeping operational entities out of the general id namespace. */
31
+ export const OPERATIONAL_ID_PREFIX = 'operational-skill-';
32
+ /** Namespaced entity id for a skill name. */
33
+ export function operationalEntityId(name) {
34
+ return `${OPERATIONAL_ID_PREFIX}${name}`;
35
+ }
36
+ /**
37
+ * Register (or repair) an operational skill in the knowledge graph.
38
+ *
39
+ * Never throws: a graph problem must not lose a skill the user already confirmed
40
+ * (AC-30). Re-running for a skill whose files exist but whose entity is missing
41
+ * recreates it, which is the AC-49 repair path.
42
+ *
43
+ * @param projectRoot - Absolute project root.
44
+ * @param skill - The skill to register.
45
+ * @returns Whether the entity is now present, plus any warning to surface.
46
+ */
47
+ export function registerOperationalSkill(projectRoot, skill) {
48
+ const id = operationalEntityId(skill.meta.name);
49
+ // Projects that never adopted the knowledge graph are not forced into it.
50
+ if (!knowledgeExists(projectRoot)) {
51
+ return {
52
+ registered: false,
53
+ id,
54
+ warning: 'knowledge graph not initialized; skipped registration (run `aidlc knowledge add` later if wanted)',
55
+ };
56
+ }
57
+ let engine;
58
+ try {
59
+ engine = openKnowledge(projectRoot);
60
+ }
61
+ catch (err) {
62
+ return {
63
+ registered: false,
64
+ id,
65
+ warning: `could not open knowledge graph: ${err.message}`,
66
+ };
67
+ }
68
+ try {
69
+ // Refuse to merge across types. mergeEntities preserves the existing `type`,
70
+ // so merging here would silently rewrite someone else's record.
71
+ const existing = engine.index.getEntity(id);
72
+ if (existing && existing.type !== OPERATIONAL_ENTITY_TYPE) {
73
+ return {
74
+ registered: false,
75
+ id,
76
+ warning: `knowledge entity '${id}' already exists with type '${existing.type}'; ` +
77
+ 'refusing to overwrite it — the skill was written but not registered',
78
+ };
79
+ }
80
+ const result = engine.writer.addEntity({
81
+ id,
82
+ type: OPERATIONAL_ENTITY_TYPE,
83
+ name: skill.meta.name,
84
+ description: `Operational skill: ${skill.meta.description}`,
85
+ source: `.aidlc/operational-skills/${skill.meta.name}.md`,
86
+ tags: ['operational-skill', 'app-owned'],
87
+ });
88
+ return {
89
+ registered: true,
90
+ id,
91
+ ...(result.warnings.length > 0 ? { warning: result.warnings.join('; ') } : {}),
92
+ };
93
+ }
94
+ catch (err) {
95
+ return {
96
+ registered: false,
97
+ id,
98
+ warning: `registration failed: ${err.message}`,
99
+ };
100
+ }
101
+ finally {
102
+ try {
103
+ engine.close();
104
+ }
105
+ catch {
106
+ // Closing is best-effort; a close failure must not mask the result.
107
+ }
108
+ }
109
+ }
110
+ /**
111
+ * Is this skill already registered with the operational type?
112
+ *
113
+ * Used to decide whether a re-run needs to repair registration (AC-49).
114
+ *
115
+ * @param projectRoot - Absolute project root.
116
+ * @param name - Skill name.
117
+ */
118
+ export function isOperationalSkillRegistered(projectRoot, name) {
119
+ if (!knowledgeExists(projectRoot))
120
+ return false;
121
+ let engine;
122
+ try {
123
+ engine = openKnowledge(projectRoot);
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ try {
129
+ const entity = engine.index.getEntity(operationalEntityId(name));
130
+ return entity?.type === OPERATIONAL_ENTITY_TYPE;
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ finally {
136
+ try {
137
+ engine.close();
138
+ }
139
+ catch {
140
+ // ignore
141
+ }
142
+ }
143
+ }
144
+ //# sourceMappingURL=knowledge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge.js","sourceRoot":"","sources":["../../src/operational/knowledge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Per-platform packaging for operational skills.
3
+ *
4
+ * Mirrors the framework adapter seam — one canonical body, N platform-native
5
+ * files — but never passes through `loadSkills`/`parseSkillFile`, which require a
6
+ * lifecycle `phase` an operational skill does not have (D-1, AC-33).
7
+ *
8
+ * Two shapes, because two platforms have no natural one-skill-one-file form:
9
+ *
10
+ * - **Per-skill** (claude-code, kiro, cursor): one pointer file per skill.
11
+ * - **Index** (codex, windsurf): one artifact listing every operational skill.
12
+ * Codex reads only `AGENTS.md`; windsurf has a per-file character budget that
13
+ * O(n) rule files would exhaust.
14
+ *
15
+ * Every platform artifact is a **pointer** to the canonical body — the full
16
+ * instructions and the verification text live in
17
+ * `.aidlc/operational-skills/<name>.md` only. That scoping is what makes
18
+ * AC-35/AC-37/AC-38 well-defined.
19
+ *
20
+ * Requirements: AC-13, AC-14, AC-15, AC-16, AC-20, AC-47, AC-48
21
+ *
22
+ * @module
23
+ */
24
+ import type { FileOp, PlatformId } from '../core/types.js';
25
+ import type { OperationalSkill } from './schema.js';
26
+ /** Directory holding canonical operational-skill bodies (AC-13, D-2). */
27
+ export declare const OPERATIONAL_DIR = ".aidlc/operational-skills";
28
+ /** Merge-block id for operational skills. Distinct from every framework id. */
29
+ export declare const OPERATIONAL_BLOCK_ID = "aidlc-operational-skills";
30
+ /** Windsurf index rule path — unprefixed, so it cannot collide with framework rules. */
31
+ export declare const WINDSURF_INDEX_PATH = ".windsurf/rules/operational-skills.md";
32
+ /**
33
+ * Build the app-owned marker comment.
34
+ *
35
+ * Placement differs per target because two adapters validate that their files
36
+ * *begin* with frontmatter (`claude-code.ts`, `cursor.ts`), so a first-line
37
+ * comment would fail their own validators.
38
+ */
39
+ export declare function ownershipMarker(name: string): string;
40
+ /** Packaging strategy for one platform. */
41
+ export interface OperationalPackager {
42
+ readonly platform: PlatformId;
43
+ /** Per-skill file ops. Empty for index-shaped platforms. */
44
+ plan(skill: OperationalSkill): FileOp[];
45
+ /** One op covering all operational skills. Absent for per-skill platforms. */
46
+ planIndex?(skills: OperationalSkill[]): FileOp | null;
47
+ }
48
+ /** Thrown when a configured platform has no operational packaging (AC-16). */
49
+ export declare class UnsupportedPlatformError extends Error {
50
+ readonly platform: string;
51
+ constructor(platform: string, supported: readonly string[]);
52
+ }
53
+ /** Relative path of a skill's canonical body. */
54
+ export declare function canonicalBodyPath(name: string): string;
55
+ /** Platforms with operational packaging, sorted for stable error messages. */
56
+ export declare const supportedOperationalPlatforms: PlatformId[];
57
+ /**
58
+ * Look up the packager for a platform.
59
+ *
60
+ * @param platform - Platform identifier from `.aidlc/config.yaml`.
61
+ * @returns The packager.
62
+ * @throws {UnsupportedPlatformError} When the platform has no packaging (AC-16).
63
+ */
64
+ export declare function packagerFor(platform: PlatformId): OperationalPackager;
65
+ /** Does this platform have operational packaging? */
66
+ export declare function hasOperationalPackaging(platform: string): boolean;
67
+ //# sourceMappingURL=packagers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packagers.d.ts","sourceRoot":"","sources":["../../src/operational/packagers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAMpD,yEAAyE;AACzE,eAAO,MAAM,eAAe,8BAA8B,CAAC;AAE3D,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,6BAA6B,CAAC;AAE/D,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,0CAA0C,CAAC;AAM3E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAWD,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,4DAA4D;IAC5D,IAAI,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAAC;IACxC,8EAA8E;IAC9E,SAAS,CAAC,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC;CACvD;AAED,8EAA8E;AAC9E,qBAAa,wBAAyB,SAAQ,KAAK;aAE/B,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM,EAChC,SAAS,EAAE,SAAS,MAAM,EAAE;CAQ/B;AAED,iDAAiD;AACjD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AA0LD,8EAA8E;AAC9E,eAAO,MAAM,6BAA6B,EAAE,UAAU,EAAiC,CAAC;AAExF;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,UAAU,GAAG,mBAAmB,CAMrE;AAED,qDAAqD;AACrD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEjE"}
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Per-platform packaging for operational skills.
3
+ *
4
+ * Mirrors the framework adapter seam — one canonical body, N platform-native
5
+ * files — but never passes through `loadSkills`/`parseSkillFile`, which require a
6
+ * lifecycle `phase` an operational skill does not have (D-1, AC-33).
7
+ *
8
+ * Two shapes, because two platforms have no natural one-skill-one-file form:
9
+ *
10
+ * - **Per-skill** (claude-code, kiro, cursor): one pointer file per skill.
11
+ * - **Index** (codex, windsurf): one artifact listing every operational skill.
12
+ * Codex reads only `AGENTS.md`; windsurf has a per-file character budget that
13
+ * O(n) rule files would exhaust.
14
+ *
15
+ * Every platform artifact is a **pointer** to the canonical body — the full
16
+ * instructions and the verification text live in
17
+ * `.aidlc/operational-skills/<name>.md` only. That scoping is what makes
18
+ * AC-35/AC-37/AC-38 well-defined.
19
+ *
20
+ * Requirements: AC-13, AC-14, AC-15, AC-16, AC-20, AC-47, AC-48
21
+ *
22
+ * @module
23
+ */
24
+ import { MAX_FILE_CHARS } from '../compile/limits.js';
25
+ import { canonicalSkillFilename, normalizeSkillName } from '../compile/naming.js';
26
+ // ---------------------------------------------------------------------------
27
+ // Constants
28
+ // ---------------------------------------------------------------------------
29
+ /** Directory holding canonical operational-skill bodies (AC-13, D-2). */
30
+ export const OPERATIONAL_DIR = '.aidlc/operational-skills';
31
+ /** Merge-block id for operational skills. Distinct from every framework id. */
32
+ export const OPERATIONAL_BLOCK_ID = 'aidlc-operational-skills';
33
+ /** Windsurf index rule path — unprefixed, so it cannot collide with framework rules. */
34
+ export const WINDSURF_INDEX_PATH = '.windsurf/rules/operational-skills.md';
35
+ // ---------------------------------------------------------------------------
36
+ // Ownership marker (AC-20)
37
+ // ---------------------------------------------------------------------------
38
+ /**
39
+ * Build the app-owned marker comment.
40
+ *
41
+ * Placement differs per target because two adapters validate that their files
42
+ * *begin* with frontmatter (`claude-code.ts`, `cursor.ts`), so a first-line
43
+ * comment would fail their own validators.
44
+ */
45
+ export function ownershipMarker(name) {
46
+ return `<!-- aidlc:operational-skill ${name} — app-owned, not framework-managed -->`;
47
+ }
48
+ /** Marker for an index artifact covering many skills. */
49
+ function indexMarker() {
50
+ return '<!-- aidlc:operational-skills — app-owned, not framework-managed -->';
51
+ }
52
+ /** Thrown when a configured platform has no operational packaging (AC-16). */
53
+ export class UnsupportedPlatformError extends Error {
54
+ platform;
55
+ constructor(platform, supported) {
56
+ super(`platform '${platform}' has no operational-skill packaging; ` +
57
+ `supported platforms are: ${supported.join(', ')}`);
58
+ this.platform = platform;
59
+ this.name = 'UnsupportedPlatformError';
60
+ }
61
+ }
62
+ /** Relative path of a skill's canonical body. */
63
+ export function canonicalBodyPath(name) {
64
+ return `${OPERATIONAL_DIR}/${canonicalSkillFilename(name)}`;
65
+ }
66
+ /** Pointer sentence shared by every platform artifact. */
67
+ function pointerLine(skill) {
68
+ return `Read \`${canonicalBodyPath(skill.meta.name)}\` and follow its instructions.`;
69
+ }
70
+ /**
71
+ * Platform directory/file segment for an operational skill.
72
+ *
73
+ * `prefix: false` is the whole point — the `aidlc-` namespace stays
74
+ * framework-owned (AC-19, AC-41).
75
+ */
76
+ function segment(name) {
77
+ return normalizeSkillName(name, { prefix: false });
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Per-skill packagers
81
+ // ---------------------------------------------------------------------------
82
+ /** claude-code: `.claude/skills/<name>/SKILL.md` (AC-14). */
83
+ const claudeCodePackager = {
84
+ platform: 'claude-code',
85
+ plan(skill) {
86
+ const dir = segment(skill.meta.name);
87
+ // Frontmatter first — claude-code's validator requires `---\n` at byte 0.
88
+ const content = [
89
+ '---',
90
+ `name: ${dir}`,
91
+ `description: ${skill.meta.description}`,
92
+ '---',
93
+ '',
94
+ ownershipMarker(skill.meta.name),
95
+ '',
96
+ pointerLine(skill),
97
+ '',
98
+ ].join('\n');
99
+ return [{ kind: 'create', path: `.claude/skills/${dir}/SKILL.md`, content }];
100
+ },
101
+ };
102
+ /** kiro: `.kiro/skills/<name>/SKILL.md`. */
103
+ const kiroPackager = {
104
+ platform: 'kiro',
105
+ plan(skill) {
106
+ const dir = segment(skill.meta.name);
107
+ const content = [
108
+ '---',
109
+ `name: ${dir}`,
110
+ `description: ${skill.meta.description}`,
111
+ '---',
112
+ '',
113
+ ownershipMarker(skill.meta.name),
114
+ '',
115
+ pointerLine(skill),
116
+ '',
117
+ ].join('\n');
118
+ return [{ kind: 'create', path: `.kiro/skills/${dir}/SKILL.md`, content }];
119
+ },
120
+ };
121
+ /**
122
+ * cursor: `.cursor/rules/<name>.mdc`.
123
+ *
124
+ * The marker rides in frontmatter as `aidlc_kind`. Cursor's validator requires
125
+ * only that `description:` and `alwaysApply:` be present, so an extra key is
126
+ * permitted — verified before relying on it.
127
+ */
128
+ const cursorPackager = {
129
+ platform: 'cursor',
130
+ plan(skill) {
131
+ const file = `${segment(skill.meta.name)}.mdc`;
132
+ const content = [
133
+ '---',
134
+ `description: ${skill.meta.description}`,
135
+ 'globs:',
136
+ 'alwaysApply: false',
137
+ 'aidlc_kind: operational',
138
+ '---',
139
+ '',
140
+ pointerLine(skill),
141
+ '',
142
+ ].join('\n');
143
+ return [{ kind: 'create', path: `.cursor/rules/${file}`, content }];
144
+ },
145
+ };
146
+ // ---------------------------------------------------------------------------
147
+ // Index packagers
148
+ // ---------------------------------------------------------------------------
149
+ /** Shared index body: marker, heading, one bullet per skill. */
150
+ function indexLines(skills) {
151
+ const lines = [indexMarker(), '', '# Operational Skills', ''];
152
+ if (skills.length === 0) {
153
+ lines.push('_No operational skills are defined in this project._');
154
+ return lines;
155
+ }
156
+ lines.push('App-owned skills for repeatable operations. Each entry points at its canonical', 'body; read that file and follow its instructions.', '');
157
+ // Sorted by name so output does not depend on directory-read order (AC-17).
158
+ for (const skill of [...skills].sort((a, b) => a.meta.name.localeCompare(b.meta.name))) {
159
+ lines.push(`- **${skill.meta.name}** — ${skill.meta.description}. ` +
160
+ `Read \`${canonicalBodyPath(skill.meta.name)}\`.`);
161
+ }
162
+ return lines;
163
+ }
164
+ /**
165
+ * codex: one merge-block in `AGENTS.md`.
166
+ *
167
+ * Shares the file with the framework block but never its id — the framework uses
168
+ * `aidlc` (`codex.ts:161`), and `mergeBlock` preserves every byte outside its own
169
+ * delimiters, so the two coexist (AC-43 as refined by DD-2).
170
+ */
171
+ const codexPackager = {
172
+ platform: 'codex',
173
+ plan() {
174
+ return [];
175
+ },
176
+ planIndex(skills) {
177
+ if (skills.length === 0)
178
+ return null;
179
+ return {
180
+ kind: 'merge-block',
181
+ path: 'AGENTS.md',
182
+ blockId: OPERATIONAL_BLOCK_ID,
183
+ content: indexLines(skills).join('\n'),
184
+ };
185
+ },
186
+ };
187
+ /**
188
+ * windsurf: one index rule file.
189
+ *
190
+ * One file regardless of skill count, but O(n) characters within it — so the
191
+ * per-file cap binds sooner, not later. On exceeding it the index degrades to a
192
+ * directory pointer rather than dropping the list, which keeps operational skills
193
+ * discoverable (US-6). The cross-plan total budget is deliberately not enforced;
194
+ * see AC-48's recorded limitation.
195
+ */
196
+ const windsurfPackager = {
197
+ platform: 'windsurf',
198
+ plan() {
199
+ return [];
200
+ },
201
+ planIndex(skills) {
202
+ if (skills.length === 0)
203
+ return null;
204
+ const full = indexLines(skills).join('\n') + '\n';
205
+ if (full.length <= MAX_FILE_CHARS) {
206
+ return { kind: 'create', path: WINDSURF_INDEX_PATH, content: full };
207
+ }
208
+ const degraded = [
209
+ indexMarker(),
210
+ '',
211
+ '# Operational Skills',
212
+ '',
213
+ `This project defines ${skills.length} app-owned operational skills — too many to`,
214
+ 'list within the windsurf rule size limit.',
215
+ '',
216
+ `Read \`${OPERATIONAL_DIR}/\` and follow the instructions in the relevant skill.`,
217
+ '',
218
+ ].join('\n');
219
+ return { kind: 'create', path: WINDSURF_INDEX_PATH, content: degraded };
220
+ },
221
+ };
222
+ // ---------------------------------------------------------------------------
223
+ // Registry
224
+ // ---------------------------------------------------------------------------
225
+ const PACKAGERS = new Map([
226
+ ['claude-code', claudeCodePackager],
227
+ ['kiro', kiroPackager],
228
+ ['cursor', cursorPackager],
229
+ ['codex', codexPackager],
230
+ ['windsurf', windsurfPackager],
231
+ ]);
232
+ /** Platforms with operational packaging, sorted for stable error messages. */
233
+ export const supportedOperationalPlatforms = [...PACKAGERS.keys()].sort();
234
+ /**
235
+ * Look up the packager for a platform.
236
+ *
237
+ * @param platform - Platform identifier from `.aidlc/config.yaml`.
238
+ * @returns The packager.
239
+ * @throws {UnsupportedPlatformError} When the platform has no packaging (AC-16).
240
+ */
241
+ export function packagerFor(platform) {
242
+ const packager = PACKAGERS.get(platform);
243
+ if (!packager) {
244
+ throw new UnsupportedPlatformError(platform, supportedOperationalPlatforms);
245
+ }
246
+ return packager;
247
+ }
248
+ /** Does this platform have operational packaging? */
249
+ export function hasOperationalPackaging(platform) {
250
+ return PACKAGERS.has(platform);
251
+ }
252
+ //# sourceMappingURL=packagers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packagers.js","sourceRoot":"","sources":["../../src/operational/packagers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Best-effort credential redaction for generated operational skills.
3
+ *
4
+ * Two rules, and the second matters as much as the first (AC-11):
5
+ *
6
+ * - Credential **values** are redacted. A generated skill is instructions an
7
+ * agent will later execute, and it is committed to the repo, so a pasted token
8
+ * would become a secret at rest (NFR-3).
9
+ * - Credential **references** are preserved. `$GITHUB_TOKEN`,
10
+ * `op://vault/item/field`, `arn:aws:secretsmanager:...` are legitimate
11
+ * operational content; stripping them would produce a skill that cannot work.
12
+ *
13
+ * **This is explicitly best-effort and not a security guarantee.** Detection is
14
+ * pattern- plus entropy-based, so novel credential formats will pass through. The
15
+ * real control is the confirmation gate: redaction runs *before* the preview is
16
+ * rendered (AC-44), so a human sees the redacted text and nothing reaches disk
17
+ * without their approval. Callers must not present this as a guarantee to users.
18
+ *
19
+ * Requirements: AC-11, AC-44, NFR-3
20
+ *
21
+ * @module
22
+ */
23
+ /** Placeholder substituted for a redacted value. */
24
+ export declare const REDACTION_PLACEHOLDER = "<REDACTED>";
25
+ /** Result of a redaction pass. */
26
+ export interface RedactionResult {
27
+ /** Text with detected credential values replaced. */
28
+ text: string;
29
+ /**
30
+ * Human-readable labels for what was redacted (never the values themselves —
31
+ * echoing them back would defeat the purpose).
32
+ */
33
+ redacted: string[];
34
+ }
35
+ /**
36
+ * Redact credential values from text, preserving credential references.
37
+ *
38
+ * @param text - Text that may contain credentials.
39
+ * @returns The redacted text plus labels describing what was removed.
40
+ */
41
+ export declare function redactSecrets(text: string): RedactionResult;
42
+ /**
43
+ * Convenience: did this text contain anything that looked like a credential?
44
+ *
45
+ * @param text - Text to inspect.
46
+ * @returns True when `redactSecrets` would change the text.
47
+ */
48
+ export declare function containsSecrets(text: string): boolean;
49
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/operational/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,oDAAoD;AACpD,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAElD,kCAAkC;AAClC,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAgED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAkC3D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Best-effort credential redaction for generated operational skills.
3
+ *
4
+ * Two rules, and the second matters as much as the first (AC-11):
5
+ *
6
+ * - Credential **values** are redacted. A generated skill is instructions an
7
+ * agent will later execute, and it is committed to the repo, so a pasted token
8
+ * would become a secret at rest (NFR-3).
9
+ * - Credential **references** are preserved. `$GITHUB_TOKEN`,
10
+ * `op://vault/item/field`, `arn:aws:secretsmanager:...` are legitimate
11
+ * operational content; stripping them would produce a skill that cannot work.
12
+ *
13
+ * **This is explicitly best-effort and not a security guarantee.** Detection is
14
+ * pattern- plus entropy-based, so novel credential formats will pass through. The
15
+ * real control is the confirmation gate: redaction runs *before* the preview is
16
+ * rendered (AC-44), so a human sees the redacted text and nothing reaches disk
17
+ * without their approval. Callers must not present this as a guarantee to users.
18
+ *
19
+ * Requirements: AC-11, AC-44, NFR-3
20
+ *
21
+ * @module
22
+ */
23
+ /** Placeholder substituted for a redacted value. */
24
+ export const REDACTION_PLACEHOLDER = '<REDACTED>';
25
+ /**
26
+ * Named provider token shapes. Anchored on distinctive prefixes so they do not
27
+ * fire on ordinary prose.
28
+ */
29
+ const TOKEN_PATTERNS = [
30
+ { label: 'GitHub token', re: /\bgh[pousr]_[A-Za-z0-9]{16,}\b/g },
31
+ { label: 'GitHub fine-grained token', re: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g },
32
+ { label: 'AWS access key id', re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g },
33
+ { label: 'Slack token', re: /\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g },
34
+ { label: 'Google API key', re: /\bAIza[0-9A-Za-z_-]{35}\b/g },
35
+ { label: 'Stripe key', re: /\b[sr]k_(?:live|test)_[0-9A-Za-z]{16,}\b/g },
36
+ { label: 'OpenAI key', re: /\bsk-[A-Za-z0-9]{20,}\b/g },
37
+ { label: 'Anthropic key', re: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g },
38
+ { label: 'npm token', re: /\bnpm_[A-Za-z0-9]{30,}\b/g },
39
+ { label: 'JSON Web Token', re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
40
+ { label: 'private key block', re: /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z ]+ )?PRIVATE KEY-----/g },
41
+ ];
42
+ /**
43
+ * `KEY=value` / `key: value` assignments whose *name* implies a secret. The value
44
+ * is redacted only when it is a literal — a `$VAR` or secret-manager reference on
45
+ * the right-hand side is a reference, not a value, and is kept.
46
+ */
47
+ const ASSIGNMENT_RE = /\b([A-Za-z_][A-Za-z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|APIKEY|API_KEY|ACCESS_KEY|PRIVATE_KEY|CREDENTIAL)[A-Za-z0-9_]*)\s*[:=]\s*(['"]?)([^\s'"#]+)\2/gi;
48
+ /**
49
+ * Right-hand sides that are references rather than values, and must survive.
50
+ * Preserving these is required by AC-11, not merely nice to have.
51
+ */
52
+ const REFERENCE_RE = /^(?:\$|\$\{|%|<|op:\/\/|vault:|secret:|arn:aws:secretsmanager:|projects\/[^/]+\/secrets\/|sm:\/\/|env:|process\.env)/i;
53
+ /** Shannon entropy in bits per character. */
54
+ function entropy(s) {
55
+ const freq = new Map();
56
+ for (const ch of s)
57
+ freq.set(ch, (freq.get(ch) ?? 0) + 1);
58
+ let h = 0;
59
+ for (const n of freq.values()) {
60
+ const p = n / s.length;
61
+ h -= p * Math.log2(p);
62
+ }
63
+ return h;
64
+ }
65
+ /**
66
+ * Does this look like a high-entropy secret rather than a word or a path?
67
+ *
68
+ * Conservative on purpose: a false positive silently mangles a legitimate skill,
69
+ * and the confirmation gate is the real backstop.
70
+ */
71
+ function looksLikeSecret(token) {
72
+ if (token.length < 24)
73
+ return false;
74
+ if (REFERENCE_RE.test(token))
75
+ return false;
76
+ // Paths, URLs, and dotted identifiers are common and are not secrets.
77
+ if (/[/\\]/.test(token) || token.includes('://'))
78
+ return false;
79
+ // Require mixed character classes — long lowercase words are not secrets.
80
+ const classes = [/[a-z]/, /[A-Z]/, /[0-9]/].filter((re) => re.test(token)).length;
81
+ if (classes < 2)
82
+ return false;
83
+ return entropy(token) >= 3.5;
84
+ }
85
+ /**
86
+ * Redact credential values from text, preserving credential references.
87
+ *
88
+ * @param text - Text that may contain credentials.
89
+ * @returns The redacted text plus labels describing what was removed.
90
+ */
91
+ export function redactSecrets(text) {
92
+ const redacted = [];
93
+ let out = text;
94
+ // 1. Named provider token shapes.
95
+ for (const { label, re } of TOKEN_PATTERNS) {
96
+ out = out.replace(re, () => {
97
+ if (!redacted.includes(label))
98
+ redacted.push(label);
99
+ return REDACTION_PLACEHOLDER;
100
+ });
101
+ }
102
+ // 2. Secret-named assignments with a literal right-hand side.
103
+ out = out.replace(ASSIGNMENT_RE, (match, key, quote, value) => {
104
+ if (REFERENCE_RE.test(value))
105
+ return match; // a reference — preserve (AC-11)
106
+ if (value === REDACTION_PLACEHOLDER)
107
+ return match; // already handled above
108
+ const label = `value assigned to ${key}`;
109
+ if (!redacted.includes(label))
110
+ redacted.push(label);
111
+ return `${key}=${quote}${REDACTION_PLACEHOLDER}${quote}`.replace('=', match.includes(':') && !match.includes('=') ? ': ' : '=');
112
+ });
113
+ // 3. Free-standing high-entropy strings.
114
+ out = out.replace(/[A-Za-z0-9+/_=-]{24,}/g, (token) => {
115
+ if (token === REDACTION_PLACEHOLDER)
116
+ return token;
117
+ if (!looksLikeSecret(token))
118
+ return token;
119
+ const label = 'high-entropy string';
120
+ if (!redacted.includes(label))
121
+ redacted.push(label);
122
+ return REDACTION_PLACEHOLDER;
123
+ });
124
+ return { text: out, redacted };
125
+ }
126
+ /**
127
+ * Convenience: did this text contain anything that looked like a credential?
128
+ *
129
+ * @param text - Text to inspect.
130
+ * @returns True when `redactSecrets` would change the text.
131
+ */
132
+ export function containsSecrets(text) {
133
+ return redactSecrets(text).redacted.length > 0;
134
+ }
135
+ //# sourceMappingURL=redact.js.map