@contextq/cli 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -5
- package/dist/index.js +105 -393
- package/package.json +9 -5
- package/dist/desktop-guide.d.ts +0 -8
- package/dist/desktop-guide.js +0 -40
- package/dist/desktop-guide.js.map +0 -1
- package/dist/detect.d.ts +0 -42
- package/dist/detect.js +0 -70
- package/dist/detect.js.map +0 -1
- package/dist/index.d.ts +0 -17
- package/dist/index.js.map +0 -1
- package/dist/mcp-config.d.ts +0 -17
- package/dist/mcp-config.js +0 -79
- package/dist/mcp-config.js.map +0 -1
- package/dist/platform-cred.d.ts +0 -105
- package/dist/platform-cred.js +0 -210
- package/dist/platform-cred.js.map +0 -1
- package/dist/prompt.d.ts +0 -8
- package/dist/prompt.js +0 -53
- package/dist/prompt.js.map +0 -1
- package/dist/session-hook.d.ts +0 -95
- package/dist/session-hook.js +0 -217
- package/dist/session-hook.js.map +0 -1
- package/dist/skills-sync.d.ts +0 -62
- package/dist/skills-sync.js +0 -129
- package/dist/skills-sync.js.map +0 -1
- package/dist/slug.d.ts +0 -48
- package/dist/slug.js +0 -62
- package/dist/slug.js.map +0 -1
- package/dist/tasks-init.d.ts +0 -14
- package/dist/tasks-init.js +0 -51
- package/dist/tasks-init.js.map +0 -1
package/dist/skills-sync.d.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* skills-sync.ts — `contextq skills sync <dir>` core logic.
|
|
3
|
-
*
|
|
4
|
-
* T698: pulls the caller's tenant-distilled `type='procedure'` contexts
|
|
5
|
-
* (T376) from the ContextQ API (`GET /api/agent/skills/export?format=json`,
|
|
6
|
-
* server-side rendering lives in src/services/skill-export.service.ts) and
|
|
7
|
-
* writes them onto disk as standard Agent Skills folders: `<dir>/<slug>/SKILL.md`.
|
|
8
|
-
*
|
|
9
|
-
* THE IDEMPOTENCY KEY IS `metadata.cluster_key`, NOT the folder name or the
|
|
10
|
-
* skill's title. `runSkillDistillation()` upserts on cluster_key and
|
|
11
|
-
* rewrites `content`/`name`/`description` IN PLACE on every scheduler tick
|
|
12
|
-
* that adds a verified session to an existing cluster -- and because the
|
|
13
|
-
* server-side slug includes a human-readable title prefix, a title rewrite
|
|
14
|
-
* can change the freshly-computed slug even though the underlying cluster
|
|
15
|
-
* (and its intended on-disk folder) is unchanged. A sync that keyed off the
|
|
16
|
-
* freshly-computed slug/title would silently FORK a second folder on the
|
|
17
|
-
* next run. This module instead reads each existing local folder's own
|
|
18
|
-
* `metadata.cluster_key` back out of its `SKILL.md` frontmatter (no
|
|
19
|
-
* separate sidecar index file to go stale) and, when a remote skill's
|
|
20
|
-
* cluster_key matches an existing folder, REWRITES THAT FOLDER IN PLACE --
|
|
21
|
-
* even if the server just handed back a different slug for it.
|
|
22
|
-
*
|
|
23
|
-
* Orphans (a local folder whose cluster_key is no longer present in the
|
|
24
|
-
* remote payload -- the source context was deleted, archived, or its
|
|
25
|
-
* consent was revoked) are REPORTED, never auto-deleted: the file may carry
|
|
26
|
-
* user edits.
|
|
27
|
-
*
|
|
28
|
-
* Network-free and fully unit-testable: `fetchSkills` is injected rather
|
|
29
|
-
* than hard-wired to `fetch()`, so tests exercise the on-disk merge logic
|
|
30
|
-
* against an in-memory payload with no server needed.
|
|
31
|
-
*/
|
|
32
|
-
export interface SkillPayload {
|
|
33
|
-
contextId: number;
|
|
34
|
-
/** Freshly computed by the server for THIS render -- may differ from the
|
|
35
|
-
* folder a matching cluster_key already lives in; see module doc. */
|
|
36
|
-
slug: string;
|
|
37
|
-
clusterKey: string;
|
|
38
|
-
markdown: string;
|
|
39
|
-
}
|
|
40
|
-
export interface SyncResult {
|
|
41
|
-
written: string[];
|
|
42
|
-
updated: string[];
|
|
43
|
-
unchanged: string[];
|
|
44
|
-
/** Folder names present on disk before this run whose cluster_key is not
|
|
45
|
-
* in the remote payload this run. Left on disk untouched. */
|
|
46
|
-
orphans: string[];
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Pull a scalar value out of the `metadata:` frontmatter block this project
|
|
50
|
-
* renders (src/services/skill-export.service.ts / yamlString()). Format is
|
|
51
|
-
* fixed and self-controlled (both write and read side live in this repo),
|
|
52
|
-
* so a small anchored regex is enough -- no general YAML parser needed.
|
|
53
|
-
* `^ <key>: "..."$` in multiline mode only matches an indented entry under
|
|
54
|
-
* `metadata:`, never the top-level `name:`/`description:` lines.
|
|
55
|
-
*/
|
|
56
|
-
export declare function extractFrontmatterMetadataValue(markdown: string, key: string): string | null;
|
|
57
|
-
export declare function extractClusterKey(markdown: string): string | null;
|
|
58
|
-
export interface SyncOptions {
|
|
59
|
-
dir: string;
|
|
60
|
-
fetchSkills: () => Promise<SkillPayload[]>;
|
|
61
|
-
}
|
|
62
|
-
export declare function syncSkills(opts: SyncOptions): Promise<SyncResult>;
|
package/dist/skills-sync.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* skills-sync.ts — `contextq skills sync <dir>` core logic.
|
|
3
|
-
*
|
|
4
|
-
* T698: pulls the caller's tenant-distilled `type='procedure'` contexts
|
|
5
|
-
* (T376) from the ContextQ API (`GET /api/agent/skills/export?format=json`,
|
|
6
|
-
* server-side rendering lives in src/services/skill-export.service.ts) and
|
|
7
|
-
* writes them onto disk as standard Agent Skills folders: `<dir>/<slug>/SKILL.md`.
|
|
8
|
-
*
|
|
9
|
-
* THE IDEMPOTENCY KEY IS `metadata.cluster_key`, NOT the folder name or the
|
|
10
|
-
* skill's title. `runSkillDistillation()` upserts on cluster_key and
|
|
11
|
-
* rewrites `content`/`name`/`description` IN PLACE on every scheduler tick
|
|
12
|
-
* that adds a verified session to an existing cluster -- and because the
|
|
13
|
-
* server-side slug includes a human-readable title prefix, a title rewrite
|
|
14
|
-
* can change the freshly-computed slug even though the underlying cluster
|
|
15
|
-
* (and its intended on-disk folder) is unchanged. A sync that keyed off the
|
|
16
|
-
* freshly-computed slug/title would silently FORK a second folder on the
|
|
17
|
-
* next run. This module instead reads each existing local folder's own
|
|
18
|
-
* `metadata.cluster_key` back out of its `SKILL.md` frontmatter (no
|
|
19
|
-
* separate sidecar index file to go stale) and, when a remote skill's
|
|
20
|
-
* cluster_key matches an existing folder, REWRITES THAT FOLDER IN PLACE --
|
|
21
|
-
* even if the server just handed back a different slug for it.
|
|
22
|
-
*
|
|
23
|
-
* Orphans (a local folder whose cluster_key is no longer present in the
|
|
24
|
-
* remote payload -- the source context was deleted, archived, or its
|
|
25
|
-
* consent was revoked) are REPORTED, never auto-deleted: the file may carry
|
|
26
|
-
* user edits.
|
|
27
|
-
*
|
|
28
|
-
* Network-free and fully unit-testable: `fetchSkills` is injected rather
|
|
29
|
-
* than hard-wired to `fetch()`, so tests exercise the on-disk merge logic
|
|
30
|
-
* against an in-memory payload with no server needed.
|
|
31
|
-
*/
|
|
32
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
33
|
-
import { join } from 'node:path';
|
|
34
|
-
/**
|
|
35
|
-
* Pull a scalar value out of the `metadata:` frontmatter block this project
|
|
36
|
-
* renders (src/services/skill-export.service.ts / yamlString()). Format is
|
|
37
|
-
* fixed and self-controlled (both write and read side live in this repo),
|
|
38
|
-
* so a small anchored regex is enough -- no general YAML parser needed.
|
|
39
|
-
* `^ <key>: "..."$` in multiline mode only matches an indented entry under
|
|
40
|
-
* `metadata:`, never the top-level `name:`/`description:` lines.
|
|
41
|
-
*/
|
|
42
|
-
export function extractFrontmatterMetadataValue(markdown, key) {
|
|
43
|
-
const re = new RegExp(`^ {2}${key}: "((?:[^"\\\\]|\\\\.)*)"$`, 'm');
|
|
44
|
-
const match = markdown.match(re);
|
|
45
|
-
if (!match)
|
|
46
|
-
return null;
|
|
47
|
-
// Reverses yamlString()'s escaping (`\` -> `\\`, `"` -> `\"`): a backslash
|
|
48
|
-
// followed by any one character collapses to that character.
|
|
49
|
-
return match[1].replace(/\\(.)/g, '$1');
|
|
50
|
-
}
|
|
51
|
-
export function extractClusterKey(markdown) {
|
|
52
|
-
return extractFrontmatterMetadataValue(markdown, 'cluster_key');
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Discover existing skill folders directly under `dir` by reading each
|
|
56
|
-
* `SKILL.md`'s own `cluster_key` back out -- this IS the persisted index;
|
|
57
|
-
* there is no separate sidecar file to go stale. A subdirectory with no
|
|
58
|
-
* `SKILL.md`, or a `SKILL.md` with no parseable `cluster_key` (not one this
|
|
59
|
-
* tool wrote, or hand-edited past recognition), is left alone entirely --
|
|
60
|
-
* never treated as ours to manage.
|
|
61
|
-
*/
|
|
62
|
-
function scanLocalSkills(dir) {
|
|
63
|
-
if (!existsSync(dir))
|
|
64
|
-
return [];
|
|
65
|
-
const entries = [];
|
|
66
|
-
for (const name of readdirSync(dir)) {
|
|
67
|
-
const folderPath = join(dir, name);
|
|
68
|
-
if (!statSync(folderPath).isDirectory())
|
|
69
|
-
continue;
|
|
70
|
-
const skillPath = join(folderPath, 'SKILL.md');
|
|
71
|
-
if (!existsSync(skillPath))
|
|
72
|
-
continue;
|
|
73
|
-
const content = readFileSync(skillPath, 'utf-8');
|
|
74
|
-
const clusterKey = extractClusterKey(content);
|
|
75
|
-
if (!clusterKey)
|
|
76
|
-
continue;
|
|
77
|
-
entries.push({ folder: name, clusterKey });
|
|
78
|
-
}
|
|
79
|
-
return entries;
|
|
80
|
-
}
|
|
81
|
-
/** Rewrite the top-level `name: <slug>` frontmatter line to match `folder`,
|
|
82
|
-
* so `frontmatter.name` stays equal to the actual directory name (Agent
|
|
83
|
-
* Skills convention) even when an existing folder is being reused under a
|
|
84
|
-
* different name than the server's freshly-computed slug. Anchored to
|
|
85
|
-
* start-of-line so it can never touch the indented `metadata:` entries. */
|
|
86
|
-
function withFrontmatterName(markdown, folder) {
|
|
87
|
-
return markdown.replace(/^name: .+$/m, `name: ${folder}`);
|
|
88
|
-
}
|
|
89
|
-
export async function syncSkills(opts) {
|
|
90
|
-
mkdirSync(opts.dir, { recursive: true });
|
|
91
|
-
const local = scanLocalSkills(opts.dir);
|
|
92
|
-
const localByClusterKey = new Map(local.map((e) => [e.clusterKey, e.folder]));
|
|
93
|
-
const usedFolders = new Set(local.map((e) => e.folder));
|
|
94
|
-
const remote = await opts.fetchSkills();
|
|
95
|
-
const result = { written: [], updated: [], unchanged: [], orphans: [] };
|
|
96
|
-
const seenClusterKeys = new Set();
|
|
97
|
-
for (const skill of remote) {
|
|
98
|
-
seenClusterKeys.add(skill.clusterKey);
|
|
99
|
-
const existingFolder = localByClusterKey.get(skill.clusterKey);
|
|
100
|
-
let folder = existingFolder;
|
|
101
|
-
if (!folder) {
|
|
102
|
-
// Brand-new cluster. Prefer the server-computed slug; only fall back
|
|
103
|
-
// to a suffixed name if an UNRELATED local folder already occupies
|
|
104
|
-
// it (astronomically unlikely given the slug's hash suffix, but
|
|
105
|
-
// deterministic and collision-safe rather than silently colliding).
|
|
106
|
-
folder = usedFolders.has(skill.slug) ? `${skill.slug}-c${skill.contextId}` : skill.slug;
|
|
107
|
-
usedFolders.add(folder);
|
|
108
|
-
localByClusterKey.set(skill.clusterKey, folder);
|
|
109
|
-
}
|
|
110
|
-
const markdown = folder === skill.slug ? skill.markdown : withFrontmatterName(skill.markdown, folder);
|
|
111
|
-
const folderPath = join(opts.dir, folder);
|
|
112
|
-
const skillPath = join(folderPath, 'SKILL.md');
|
|
113
|
-
const previous = existsSync(skillPath) ? readFileSync(skillPath, 'utf-8') : null;
|
|
114
|
-
if (previous === markdown) {
|
|
115
|
-
result.unchanged.push(folder);
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
mkdirSync(folderPath, { recursive: true });
|
|
119
|
-
writeFileSync(skillPath, markdown, 'utf-8');
|
|
120
|
-
result[previous === null ? 'written' : 'updated'].push(folder);
|
|
121
|
-
}
|
|
122
|
-
for (const entry of local) {
|
|
123
|
-
if (!seenClusterKeys.has(entry.clusterKey)) {
|
|
124
|
-
result.orphans.push(entry.folder);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
|
-
//# sourceMappingURL=skills-sync.js.map
|
package/dist/skills-sync.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skills-sync.js","sourceRoot":"","sources":["../src/skills-sync.ts"],"names":[],"mappings":"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"}
|
package/dist/slug.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* slug.ts — shared slug validation/sanitization for --agent / --workspace.
|
|
3
|
-
*
|
|
4
|
-
* T656: the installer used to accept --agent / --workspace verbatim (or fall
|
|
5
|
-
* back to basename(root), a directory name that can legally contain shell
|
|
6
|
-
* metacharacters) and interpolate them, unescaped, into a shell command
|
|
7
|
-
* string written into .claude/settings.local.json's SessionStart hook. That
|
|
8
|
-
* command runs through a shell on every future session start in the target
|
|
9
|
-
* repo, so an unsanitized value there is an auto-executing injection.
|
|
10
|
-
*
|
|
11
|
-
* SLUG_PATTERN mirrors the slug shape the server itself converges on: every
|
|
12
|
-
* `slugify()` in the API (src/services/workspace.service.ts,
|
|
13
|
-
* context.service.ts, tenant-slug.ts) lowercases, then collapses any run of
|
|
14
|
-
* non [a-z0-9] chars to a single '-', then trims leading/trailing '-'. That
|
|
15
|
-
* output is always a subset of [a-z0-9][a-z0-9_-]{0,63} — this pattern adds
|
|
16
|
-
* '_' too (never produced by slugify(), but a common human-typed separator)
|
|
17
|
-
* and stays deliberately ASCII-lowercase-only so the exact same pattern can
|
|
18
|
-
* gate both --agent and --workspace with one rule: CLAUDE.md documents
|
|
19
|
-
* "Workspace slugs are always lowercase" as a product convention, and this
|
|
20
|
-
* installer extends that same lowercase-only discipline to --agent too
|
|
21
|
-
* rather than maintaining two different regexes for two fields that both
|
|
22
|
-
* end up as shell-interpolated env var values. This is a deliberately
|
|
23
|
-
* STRICTER-than-the-API client-side gate (the boot API's `agent` field is
|
|
24
|
-
* just `z.string().min(1).max(64)` with no charset check server-side) — the
|
|
25
|
-
* goal here is never producing a value the API would reject, not permitting
|
|
26
|
-
* everything the API happens to tolerate.
|
|
27
|
-
*/
|
|
28
|
-
export declare const SLUG_PATTERN: RegExp;
|
|
29
|
-
export declare const SLUG_PATTERN_DESCRIPTION = "lowercase letters, digits, \"-\", \"_\" (must start with a letter/digit, max 64 chars)";
|
|
30
|
-
/**
|
|
31
|
-
* Validates an EXPLICIT (user-typed) flag value against SLUG_PATTERN.
|
|
32
|
-
* Throws a clear, actionable error naming the flag, the offending value, and
|
|
33
|
-
* the allowed pattern -- never silently truncates or mutates an explicit
|
|
34
|
-
* value (the caller may choose to lowercase --workspace first, since
|
|
35
|
-
* workspace slugs are documented as always-lowercase in this product; see
|
|
36
|
-
* index.ts). No such courtesy applies to --agent, which has no such
|
|
37
|
-
* documented casing convention.
|
|
38
|
-
*/
|
|
39
|
-
export declare function validateSlug(value: string, flagName: string): string;
|
|
40
|
-
/**
|
|
41
|
-
* Sanitizes a FALLBACK value (basename(root) -- the user never typed this,
|
|
42
|
-
* it came from the filesystem) into something that always satisfies
|
|
43
|
-
* SLUG_PATTERN: lowercase, replace every disallowed run with a single '-',
|
|
44
|
-
* trim leading/trailing separators, cap at 64 chars. An empty result (e.g.
|
|
45
|
-
* a directory name made entirely of disallowed characters) falls back to
|
|
46
|
-
* the fixed default 'default' rather than producing an empty/invalid slug.
|
|
47
|
-
*/
|
|
48
|
-
export declare function sanitizeSlugFallback(raw: string): string;
|
package/dist/slug.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* slug.ts — shared slug validation/sanitization for --agent / --workspace.
|
|
3
|
-
*
|
|
4
|
-
* T656: the installer used to accept --agent / --workspace verbatim (or fall
|
|
5
|
-
* back to basename(root), a directory name that can legally contain shell
|
|
6
|
-
* metacharacters) and interpolate them, unescaped, into a shell command
|
|
7
|
-
* string written into .claude/settings.local.json's SessionStart hook. That
|
|
8
|
-
* command runs through a shell on every future session start in the target
|
|
9
|
-
* repo, so an unsanitized value there is an auto-executing injection.
|
|
10
|
-
*
|
|
11
|
-
* SLUG_PATTERN mirrors the slug shape the server itself converges on: every
|
|
12
|
-
* `slugify()` in the API (src/services/workspace.service.ts,
|
|
13
|
-
* context.service.ts, tenant-slug.ts) lowercases, then collapses any run of
|
|
14
|
-
* non [a-z0-9] chars to a single '-', then trims leading/trailing '-'. That
|
|
15
|
-
* output is always a subset of [a-z0-9][a-z0-9_-]{0,63} — this pattern adds
|
|
16
|
-
* '_' too (never produced by slugify(), but a common human-typed separator)
|
|
17
|
-
* and stays deliberately ASCII-lowercase-only so the exact same pattern can
|
|
18
|
-
* gate both --agent and --workspace with one rule: CLAUDE.md documents
|
|
19
|
-
* "Workspace slugs are always lowercase" as a product convention, and this
|
|
20
|
-
* installer extends that same lowercase-only discipline to --agent too
|
|
21
|
-
* rather than maintaining two different regexes for two fields that both
|
|
22
|
-
* end up as shell-interpolated env var values. This is a deliberately
|
|
23
|
-
* STRICTER-than-the-API client-side gate (the boot API's `agent` field is
|
|
24
|
-
* just `z.string().min(1).max(64)` with no charset check server-side) — the
|
|
25
|
-
* goal here is never producing a value the API would reject, not permitting
|
|
26
|
-
* everything the API happens to tolerate.
|
|
27
|
-
*/
|
|
28
|
-
export const SLUG_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
29
|
-
export const SLUG_PATTERN_DESCRIPTION = 'lowercase letters, digits, "-", "_" (must start with a letter/digit, max 64 chars)';
|
|
30
|
-
/**
|
|
31
|
-
* Validates an EXPLICIT (user-typed) flag value against SLUG_PATTERN.
|
|
32
|
-
* Throws a clear, actionable error naming the flag, the offending value, and
|
|
33
|
-
* the allowed pattern -- never silently truncates or mutates an explicit
|
|
34
|
-
* value (the caller may choose to lowercase --workspace first, since
|
|
35
|
-
* workspace slugs are documented as always-lowercase in this product; see
|
|
36
|
-
* index.ts). No such courtesy applies to --agent, which has no such
|
|
37
|
-
* documented casing convention.
|
|
38
|
-
*/
|
|
39
|
-
export function validateSlug(value, flagName) {
|
|
40
|
-
if (!SLUG_PATTERN.test(value)) {
|
|
41
|
-
throw new Error(`${flagName} value "${value}" is not a valid slug -- allowed: ${SLUG_PATTERN_DESCRIPTION}`);
|
|
42
|
-
}
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Sanitizes a FALLBACK value (basename(root) -- the user never typed this,
|
|
47
|
-
* it came from the filesystem) into something that always satisfies
|
|
48
|
-
* SLUG_PATTERN: lowercase, replace every disallowed run with a single '-',
|
|
49
|
-
* trim leading/trailing separators, cap at 64 chars. An empty result (e.g.
|
|
50
|
-
* a directory name made entirely of disallowed characters) falls back to
|
|
51
|
-
* the fixed default 'default' rather than producing an empty/invalid slug.
|
|
52
|
-
*/
|
|
53
|
-
export function sanitizeSlugFallback(raw) {
|
|
54
|
-
let out = raw
|
|
55
|
-
.toLowerCase()
|
|
56
|
-
.replace(/[^a-z0-9_-]+/g, '-')
|
|
57
|
-
.replace(/^[-_]+|[-_]+$/g, '');
|
|
58
|
-
out = out.slice(0, 64);
|
|
59
|
-
out = out.replace(/[-_]+$/g, '');
|
|
60
|
-
return out === '' ? 'default' : out;
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=slug.js.map
|
package/dist/slug.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"slug.js","sourceRoot":"","sources":["../src/slug.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAEzD,MAAM,CAAC,MAAM,wBAAwB,GAAG,oFAAoF,CAAC;AAE7H;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,QAAgB;IAC1D,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,GAAG,QAAQ,WAAW,KAAK,qCAAqC,wBAAwB,EAAE,CAC3F,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,IAAI,GAAG,GAAG,GAAG;SACV,WAAW,EAAE;SACb,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC;SAC7B,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;IAEjC,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACvB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAEjC,OAAO,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC;AACtC,CAAC"}
|
package/dist/tasks-init.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tasks-init.ts — scaffold .contextq/tasks/TASKS.md.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors skills/contextq/scripts/ctq-tasks-init.sh 1:1 (same manifest
|
|
5
|
-
* header, same idempotent "leave it alone if it exists" behavior, same
|
|
6
|
-
* narrow 3-line .gitignore patch for the churny per-machine overlay files).
|
|
7
|
-
* Does not touch anything else in .gitignore.
|
|
8
|
-
*/
|
|
9
|
-
export interface TasksInitResult {
|
|
10
|
-
path: string;
|
|
11
|
-
action: 'created' | 'skipped';
|
|
12
|
-
detail: string;
|
|
13
|
-
}
|
|
14
|
-
export declare function initTasksDir(root: string): TasksInitResult;
|
package/dist/tasks-init.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tasks-init.ts — scaffold .contextq/tasks/TASKS.md.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors skills/contextq/scripts/ctq-tasks-init.sh 1:1 (same manifest
|
|
5
|
-
* header, same idempotent "leave it alone if it exists" behavior, same
|
|
6
|
-
* narrow 3-line .gitignore patch for the churny per-machine overlay files).
|
|
7
|
-
* Does not touch anything else in .gitignore.
|
|
8
|
-
*/
|
|
9
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
|
-
import { basename, join } from 'node:path';
|
|
11
|
-
function manifestHeader(project) {
|
|
12
|
-
return `# Tasks: ${project}
|
|
13
|
-
<!-- ctq-tasks manifest v1 (T442, repo-local fork of nf-tasks). Status tokens: [backlog] [todo] [doing] [done] [forbidden]. -->
|
|
14
|
-
<!-- "blocked" is DERIVED — never a manual status. Two kinds: deps not all [done], OR a -->
|
|
15
|
-
<!-- "blocker: <reason>" line on a [todo] (external wait); clear the line to release it. -->
|
|
16
|
-
<!-- [backlog] = not ready yet; excluded from run planning. Promote with: flip todo <id> -->
|
|
17
|
-
<!-- [forbidden] = too dangerous to ever run — permanent tombstone. Add 'reason: <why>'. -->
|
|
18
|
-
<!-- Never archived. Flip back to todo only if mis-classified. -->
|
|
19
|
-
<!-- Set with: flip forbidden <id> -->
|
|
20
|
-
<!-- Task header format is parsed: "## T<NN> [status] <free-text title>". Keep the id+status shape. -->
|
|
21
|
-
<!-- See docs/tech/ctq-tasks-sync.md for the field mapping this manifest syncs against. -->
|
|
22
|
-
|
|
23
|
-
`;
|
|
24
|
-
}
|
|
25
|
-
function patchGitignore(root) {
|
|
26
|
-
if (!existsSync(join(root, '.git')))
|
|
27
|
-
return;
|
|
28
|
-
const gitignorePath = join(root, '.gitignore');
|
|
29
|
-
const patterns = ['.contextq/tasks/status.log', '.contextq/tasks/.reconcile.lock/', '.contextq/tasks/sync-map.json'];
|
|
30
|
-
const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';
|
|
31
|
-
const existingLines = new Set(existing.split('\n').map((line) => line.trim()));
|
|
32
|
-
const missing = patterns.filter((p) => !existingLines.has(p));
|
|
33
|
-
if (missing.length === 0)
|
|
34
|
-
return;
|
|
35
|
-
const block = '\n# .contextq/tasks/ — TASKS.md + TASKS_ARCHIVE.md are tracked; the\n' +
|
|
36
|
-
'# overlay/lock/sync-map below are machine-local and ephemeral\n' +
|
|
37
|
-
missing.map((p) => `${p}\n`).join('');
|
|
38
|
-
appendFileSync(gitignorePath, block);
|
|
39
|
-
}
|
|
40
|
-
export function initTasksDir(root) {
|
|
41
|
-
const tasksDir = join(root, '.contextq', 'tasks');
|
|
42
|
-
const manifest = join(tasksDir, 'TASKS.md');
|
|
43
|
-
mkdirSync(tasksDir, { recursive: true });
|
|
44
|
-
patchGitignore(root);
|
|
45
|
-
if (existsSync(manifest)) {
|
|
46
|
-
return { path: manifest, action: 'skipped', detail: 'manifest already exists — left untouched' };
|
|
47
|
-
}
|
|
48
|
-
writeFileSync(manifest, manifestHeader(basename(root)));
|
|
49
|
-
return { path: manifest, action: 'created', detail: 'task manifest scaffolded' };
|
|
50
|
-
}
|
|
51
|
-
//# sourceMappingURL=tasks-init.js.map
|
package/dist/tasks-init.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tasks-init.js","sourceRoot":"","sources":["../src/tasks-init.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7F,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQ3C,SAAS,cAAc,CAAC,OAAe;IACrC,OAAO,YAAY,OAAO;;;;;;;;;;;CAW3B,CAAC;AACF,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAAE,OAAO;IAE5C,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,CAAC,4BAA4B,EAAE,kCAAkC,EAAE,+BAA+B,CAAC,CAAC;IAErH,MAAM,QAAQ,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvF,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/E,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEjC,MAAM,KAAK,GACT,uEAAuE;QACvE,iEAAiE;QACjE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAExC,cAAc,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAE5C,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,cAAc,CAAC,IAAI,CAAC,CAAC;IAErB,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,0CAA0C,EAAE,CAAC;IACnG,CAAC;IAED,aAAa,CAAC,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC;AACnF,CAAC"}
|