@agent-workspace/utils 0.7.0 → 0.8.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE5E;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,SAAS,eAAe,EAChE,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAQ3B;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,eAAe,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,CAEhG;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,SAAS,eAAe,EAChE,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,GACrB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,CAEpF"}
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG5E;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,SAAS,eAAe,EAChE,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAQ3B;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,eAAe,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,CAEhG;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,SAAS,eAAe,EAChE,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,GACrB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,CAEpF"}
@@ -1,5 +1,6 @@
1
- import { readFile, writeFile } from "node:fs/promises";
1
+ import { readFile } from "node:fs/promises";
2
2
  import matter from "gray-matter";
3
+ import { atomicWriteFile } from "./safe-io.js";
3
4
  /**
4
5
  * Parse an AWP workspace file (Markdown with YAML frontmatter).
5
6
  *
@@ -32,7 +33,7 @@ export function serializeWorkspaceFile(file) {
32
33
  */
33
34
  export async function writeWorkspaceFile(file) {
34
35
  const content = serializeWorkspaceFile(file);
35
- await writeFile(file.filePath, content, "utf-8");
36
+ await atomicWriteFile(file.filePath, content);
36
37
  }
37
38
  /**
38
39
  * Extract the type from frontmatter data.
@@ -1 +1 @@
1
- {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,MAAM,MAAM,aAAa,CAAC;AAGjC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAAgB;IAEhB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtC,OAAO;QACL,WAAW,EAAE,IAAS;QACtB,IAAI,EAAE,OAAO;QACb,QAAQ;KACT,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAA4B,IAAsB;IACtF,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAsB;IAEtB,MAAM,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAA6B;IAC9D,OAAO,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC"}
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAAgB;IAEhB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtC,OAAO;QACL,WAAW,EAAE,IAAS;QACtB,IAAI,EAAE,OAAO;QACb,QAAQ;KACT,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAA4B,IAAsB;IACtF,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAsB;IAEtB,MAAM,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAA6B;IAC9D,OAAO,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC"}
package/dist/index.d.ts CHANGED
@@ -4,10 +4,11 @@
4
4
  * Shared utilities for AWP packages including validation,
5
5
  * reputation calculations, frontmatter parsing, and workspace helpers.
6
6
  */
7
- export { validateSlug, sanitizeSlug, validatePath, isValidDate, isValidTimestamp, } from "./validation.js";
7
+ export { validateSlug, sanitizeSlug, validatePath, isValidDate, isValidTimestamp, VALID_TASK_STATUSES, VALID_PROJECT_STATUSES, VALID_PROVENANCE_ACTIONS, VALID_PRIORITIES, normalizeTaskStatus, normalizeProvenanceAction, suggestValidValue, } from "./validation.js";
8
8
  export { computeConfidence, computeDecayedScore, updateDimension, computeWeightedScore, } from "./reputation.js";
9
9
  export { parseWorkspaceFile, serializeWorkspaceFile, writeWorkspaceFile, getFrontmatterType, } from "./frontmatter.js";
10
10
  export { findWorkspaceRoot, loadManifest, fileExists, getAgentDid, safeReadFile, getWorkspaceRoot, } from "./workspace.js";
11
11
  export { type TaskNode, type DependencyGraph, type GraphAnalysis, buildGraph, topologicalSort, detectCycles, findCriticalPath, getBlockedTasks, analyzeGraph, getTaskSlug, getProjectSlug, } from "./graph.js";
12
+ export { atomicWriteFile, withFileLock, safeWriteJson, loadJsonFile, type AtomicWriteOptions, type FileLockOptions, } from "./safe-io.js";
12
13
  export { type RecruitmentCandidate, type RecruitmentResult, findCandidatesForRole, autoRecruitSwarm, isSwarmFullyStaffed, getSwarmStaffingSummary, } from "./swarm.js";
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,UAAU,EACV,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,cAAc,GACf,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,gBAAgB,EAEhB,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,UAAU,EACV,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,cAAc,GACf,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,eAAe,EACf,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -5,7 +5,9 @@
5
5
  * reputation calculations, frontmatter parsing, and workspace helpers.
6
6
  */
7
7
  // Validation utilities
8
- export { validateSlug, sanitizeSlug, validatePath, isValidDate, isValidTimestamp, } from "./validation.js";
8
+ export { validateSlug, sanitizeSlug, validatePath, isValidDate, isValidTimestamp,
9
+ // Agent-friendly normalization
10
+ VALID_TASK_STATUSES, VALID_PROJECT_STATUSES, VALID_PROVENANCE_ACTIONS, VALID_PRIORITIES, normalizeTaskStatus, normalizeProvenanceAction, suggestValidValue, } from "./validation.js";
9
11
  // Reputation utilities
10
12
  export { computeConfidence, computeDecayedScore, updateDimension, computeWeightedScore, } from "./reputation.js";
11
13
  // Frontmatter utilities
@@ -14,6 +16,8 @@ export { parseWorkspaceFile, serializeWorkspaceFile, writeWorkspaceFile, getFron
14
16
  export { findWorkspaceRoot, loadManifest, fileExists, getAgentDid, safeReadFile, getWorkspaceRoot, } from "./workspace.js";
15
17
  // Graph utilities (dependency analysis)
16
18
  export { buildGraph, topologicalSort, detectCycles, findCriticalPath, getBlockedTasks, analyzeGraph, getTaskSlug, getProjectSlug, } from "./graph.js";
19
+ // Safe I/O utilities
20
+ export { atomicWriteFile, withFileLock, safeWriteJson, loadJsonFile, } from "./safe-io.js";
17
21
  // Swarm utilities (recruitment)
18
22
  export { findCandidatesForRole, autoRecruitSwarm, isSwarmFullyStaffed, getSwarmStaffingSummary, } from "./swarm.js";
19
23
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,uBAAuB;AACvB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAEzB,uBAAuB;AACvB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAEzB,wBAAwB;AACxB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,sBAAsB;AACtB,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAExB,wCAAwC;AACxC,OAAO,EAIL,UAAU,EACV,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,gCAAgC;AAChC,OAAO,EAGL,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,uBAAuB;AACvB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,gBAAgB;AAChB,+BAA+B;AAC/B,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAEzB,uBAAuB;AACvB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAEzB,wBAAwB;AACxB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,sBAAsB;AACtB,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAExB,wCAAwC;AACxC,OAAO,EAIL,UAAU,EACV,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,qBAAqB;AACrB,OAAO,EACL,eAAe,EACf,YAAY,EACZ,aAAa,EACb,YAAY,GAGb,MAAM,cAAc,CAAC;AAEtB,gCAAgC;AAChC,OAAO,EAGL,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Safe file I/O utilities inspired by clawdbot.
3
+ *
4
+ * Provides atomic writes (temp file + rename), file locking with stale
5
+ * detection, backup rotation, and safe JSON helpers.
6
+ */
7
+ export interface AtomicWriteOptions {
8
+ /** Enable backup rotation on overwrite (default: true) */
9
+ backup?: boolean;
10
+ /** Maximum number of backup copies to keep (default: 5) */
11
+ maxBackups?: number;
12
+ }
13
+ export interface FileLockOptions {
14
+ /** Lock acquisition timeout in milliseconds (default: 10_000) */
15
+ timeout?: number;
16
+ /** Age in milliseconds after which a lock is considered stale (default: 1_800_000 = 30min) */
17
+ staleThreshold?: number;
18
+ }
19
+ /**
20
+ * Write a file atomically using temp file + rename.
21
+ *
22
+ * - Writes content to a uniquely-named temp file
23
+ * - Backs up the existing file with rotation (if it exists)
24
+ * - Atomically renames the temp file to the target
25
+ * - Falls back to copyFile on cross-device or Windows errors
26
+ * - Cleans up the temp file on failure
27
+ */
28
+ export declare function atomicWriteFile(filePath: string, content: string, options?: AtomicWriteOptions): Promise<void>;
29
+ /**
30
+ * Execute a function while holding an exclusive file lock.
31
+ *
32
+ * - Uses `fs.open(lockPath, "wx")` for exclusive creation
33
+ * - Supports nested lock counting (same process, same file)
34
+ * - Detects stale locks via timestamp age and PID liveness
35
+ * - Retries with exponential backoff up to timeout
36
+ * - Always releases lock in finally block
37
+ */
38
+ export declare function withFileLock<T>(filePath: string, fn: () => Promise<T>, options?: FileLockOptions): Promise<T>;
39
+ /**
40
+ * Write a JSON file atomically with directory creation.
41
+ */
42
+ export declare function safeWriteJson(filePath: string, data: unknown, options?: AtomicWriteOptions): Promise<void>;
43
+ /**
44
+ * Load and parse a JSON file, returning undefined if missing or corrupt.
45
+ */
46
+ export declare function loadJsonFile<T = unknown>(filePath: string): Promise<T | undefined>;
47
+ //# sourceMappingURL=safe-io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-io.d.ts","sourceRoot":"","sources":["../src/safe-io.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAwED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAqCf;AAMD;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,CAAC,CAAC,CA2FZ;AAMD;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAGf;AAMD;;GAEG;AACH,wBAAsB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAWxF"}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Safe file I/O utilities inspired by clawdbot.
3
+ *
4
+ * Provides atomic writes (temp file + rename), file locking with stale
5
+ * detection, backup rotation, and safe JSON helpers.
6
+ */
7
+ import { mkdir, writeFile, readFile, rename, copyFile, unlink, open, rm } from "node:fs/promises";
8
+ import { existsSync } from "node:fs";
9
+ import { dirname, basename, join, resolve } from "node:path";
10
+ import { randomUUID } from "node:crypto";
11
+ const HELD_LOCKS = new Map();
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // Helpers
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+ /**
16
+ * Check if a process is still alive via signal 0.
17
+ */
18
+ function isProcessAlive(pid) {
19
+ if (!Number.isFinite(pid) || pid <= 0)
20
+ return false;
21
+ try {
22
+ process.kill(pid, 0);
23
+ return true;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ /**
30
+ * Read and parse lock file payload.
31
+ */
32
+ async function readLockPayload(lockPath) {
33
+ try {
34
+ const raw = await readFile(lockPath, "utf-8");
35
+ const parsed = JSON.parse(raw);
36
+ if (typeof parsed.pid !== "number" || typeof parsed.createdAt !== "string")
37
+ return null;
38
+ return { pid: parsed.pid, createdAt: parsed.createdAt };
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ /**
45
+ * Rotate backup files: .bak → .bak.1, .bak.1 → .bak.2, etc.
46
+ * Deletes the oldest backup if at max capacity.
47
+ */
48
+ async function rotateBackups(filePath, maxBackups) {
49
+ if (maxBackups <= 1)
50
+ return;
51
+ const backupBase = `${filePath}.bak`;
52
+ const maxIndex = maxBackups - 1;
53
+ // Delete oldest
54
+ await unlink(`${backupBase}.${maxIndex}`).catch(() => { });
55
+ // Shift all down
56
+ for (let i = maxIndex - 1; i >= 1; i--) {
57
+ await rename(`${backupBase}.${i}`, `${backupBase}.${i + 1}`).catch(() => { });
58
+ }
59
+ // Move current .bak to .bak.1
60
+ await rename(backupBase, `${backupBase}.1`).catch(() => { });
61
+ }
62
+ // ─────────────────────────────────────────────────────────────────────────────
63
+ // atomicWriteFile
64
+ // ─────────────────────────────────────────────────────────────────────────────
65
+ /**
66
+ * Write a file atomically using temp file + rename.
67
+ *
68
+ * - Writes content to a uniquely-named temp file
69
+ * - Backs up the existing file with rotation (if it exists)
70
+ * - Atomically renames the temp file to the target
71
+ * - Falls back to copyFile on cross-device or Windows errors
72
+ * - Cleans up the temp file on failure
73
+ */
74
+ export async function atomicWriteFile(filePath, content, options) {
75
+ const doBackup = options?.backup !== false;
76
+ const maxBackups = options?.maxBackups ?? 5;
77
+ const dir = dirname(filePath);
78
+ const base = basename(filePath);
79
+ const tmp = join(dir, `${base}.${process.pid}.${randomUUID()}.tmp`);
80
+ // Ensure directory exists
81
+ await mkdir(dir, { recursive: true });
82
+ // Write to temp file
83
+ await writeFile(tmp, content, { encoding: "utf-8" });
84
+ try {
85
+ // Backup existing file
86
+ if (doBackup && existsSync(filePath)) {
87
+ await rotateBackups(filePath, maxBackups);
88
+ await copyFile(filePath, `${filePath}.bak`).catch(() => { });
89
+ }
90
+ // Atomic rename
91
+ await rename(tmp, filePath);
92
+ }
93
+ catch (err) {
94
+ const code = err.code;
95
+ // Cross-device rename or Windows permission error: fall back to copy
96
+ if (code === "EXDEV" || code === "EPERM" || code === "EEXIST") {
97
+ await copyFile(tmp, filePath);
98
+ await unlink(tmp).catch(() => { });
99
+ return;
100
+ }
101
+ // Clean up temp file on unexpected error
102
+ await unlink(tmp).catch(() => { });
103
+ throw err;
104
+ }
105
+ }
106
+ // ─────────────────────────────────────────────────────────────────────────────
107
+ // withFileLock
108
+ // ─────────────────────────────────────────────────────────────────────────────
109
+ /**
110
+ * Execute a function while holding an exclusive file lock.
111
+ *
112
+ * - Uses `fs.open(lockPath, "wx")` for exclusive creation
113
+ * - Supports nested lock counting (same process, same file)
114
+ * - Detects stale locks via timestamp age and PID liveness
115
+ * - Retries with exponential backoff up to timeout
116
+ * - Always releases lock in finally block
117
+ */
118
+ export async function withFileLock(filePath, fn, options) {
119
+ const timeout = options?.timeout ?? 10_000;
120
+ const staleThreshold = options?.staleThreshold ?? 30 * 60 * 1000;
121
+ const normalizedPath = resolve(filePath);
122
+ const lockPath = `${normalizedPath}.lock`;
123
+ // Check for nested lock (same process already holds this lock)
124
+ const held = HELD_LOCKS.get(normalizedPath);
125
+ if (held) {
126
+ held.count++;
127
+ try {
128
+ return await fn();
129
+ }
130
+ finally {
131
+ held.count--;
132
+ if (held.count <= 0) {
133
+ HELD_LOCKS.delete(normalizedPath);
134
+ await rm(held.lockPath, { force: true }).catch(() => { });
135
+ }
136
+ }
137
+ }
138
+ // Acquire new lock
139
+ const startedAt = Date.now();
140
+ let attempt = 0;
141
+ while (Date.now() - startedAt < timeout) {
142
+ attempt++;
143
+ try {
144
+ // Ensure directory exists
145
+ await mkdir(dirname(lockPath), { recursive: true });
146
+ // Exclusive creation — fails with EEXIST if lock already held
147
+ const handle = await open(lockPath, "wx");
148
+ const payload = JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }, null, 2);
149
+ await handle.writeFile(payload, "utf-8");
150
+ await handle.close();
151
+ // Register in-process lock
152
+ HELD_LOCKS.set(normalizedPath, { count: 1, lockPath });
153
+ try {
154
+ return await fn();
155
+ }
156
+ finally {
157
+ const current = HELD_LOCKS.get(normalizedPath);
158
+ if (current) {
159
+ current.count--;
160
+ if (current.count <= 0) {
161
+ HELD_LOCKS.delete(normalizedPath);
162
+ await rm(current.lockPath, { force: true }).catch(() => { });
163
+ }
164
+ }
165
+ }
166
+ }
167
+ catch (err) {
168
+ const code = err.code;
169
+ if (code !== "EEXIST")
170
+ throw err;
171
+ // Lock file exists — check if stale
172
+ const lockPayload = await readLockPayload(lockPath);
173
+ if (lockPayload) {
174
+ const createdAt = Date.parse(lockPayload.createdAt);
175
+ const isStale = !Number.isFinite(createdAt) || Date.now() - createdAt > staleThreshold;
176
+ const isAlive = isProcessAlive(lockPayload.pid);
177
+ if (isStale || !isAlive) {
178
+ // Remove stale lock and retry immediately
179
+ await rm(lockPath, { force: true }).catch(() => { });
180
+ continue;
181
+ }
182
+ }
183
+ else {
184
+ // Can't read lock file — remove and retry
185
+ await rm(lockPath, { force: true }).catch(() => { });
186
+ continue;
187
+ }
188
+ // Lock is held by a live process — wait with backoff
189
+ const delay = Math.min(1000, 50 * attempt);
190
+ await new Promise((r) => setTimeout(r, delay));
191
+ }
192
+ }
193
+ // Timeout
194
+ const lockPayload = await readLockPayload(lockPath);
195
+ const owner = lockPayload?.pid ? `pid=${lockPayload.pid}` : "unknown";
196
+ throw new Error(`File lock timeout (${timeout}ms): ${owner} holds ${lockPath}`);
197
+ }
198
+ // ─────────────────────────────────────────────────────────────────────────────
199
+ // safeWriteJson
200
+ // ─────────────────────────────────────────────────────────────────────────────
201
+ /**
202
+ * Write a JSON file atomically with directory creation.
203
+ */
204
+ export async function safeWriteJson(filePath, data, options) {
205
+ await mkdir(dirname(filePath), { recursive: true });
206
+ await atomicWriteFile(filePath, JSON.stringify(data, null, 2) + "\n", options);
207
+ }
208
+ // ─────────────────────────────────────────────────────────────────────────────
209
+ // loadJsonFile
210
+ // ─────────────────────────────────────────────────────────────────────────────
211
+ /**
212
+ * Load and parse a JSON file, returning undefined if missing or corrupt.
213
+ */
214
+ export async function loadJsonFile(filePath) {
215
+ try {
216
+ const raw = await readFile(filePath, "utf-8");
217
+ return JSON.parse(raw);
218
+ }
219
+ catch (err) {
220
+ const code = err.code;
221
+ if (code === "ENOENT" || err instanceof SyntaxError) {
222
+ return undefined;
223
+ }
224
+ throw err;
225
+ }
226
+ }
227
+ //# sourceMappingURL=safe-io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-io.js","sourceRoot":"","sources":["../src/safe-io.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=safe-io.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-io.test.d.ts","sourceRoot":"","sources":["../src/safe-io.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,196 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdtemp, readFile, writeFile, readdir, rm, mkdir } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { atomicWriteFile, withFileLock, safeWriteJson, loadJsonFile } from "./safe-io.js";
7
+ let testDir;
8
+ beforeEach(async () => {
9
+ testDir = await mkdtemp(join(tmpdir(), "awp-safe-io-test-"));
10
+ });
11
+ afterEach(async () => {
12
+ await rm(testDir, { recursive: true, force: true });
13
+ });
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+ // atomicWriteFile
16
+ // ─────────────────────────────────────────────────────────────────────────────
17
+ describe("atomicWriteFile", () => {
18
+ it("writes file content correctly", async () => {
19
+ const filePath = join(testDir, "test.md");
20
+ await atomicWriteFile(filePath, "hello world");
21
+ const content = await readFile(filePath, "utf-8");
22
+ expect(content).toBe("hello world");
23
+ });
24
+ it("creates parent directories", async () => {
25
+ const filePath = join(testDir, "a", "b", "c", "test.md");
26
+ await atomicWriteFile(filePath, "nested");
27
+ const content = await readFile(filePath, "utf-8");
28
+ expect(content).toBe("nested");
29
+ });
30
+ it("creates .bak on overwrite", async () => {
31
+ const filePath = join(testDir, "test.md");
32
+ await writeFile(filePath, "original", "utf-8");
33
+ await atomicWriteFile(filePath, "updated");
34
+ expect(await readFile(filePath, "utf-8")).toBe("updated");
35
+ expect(await readFile(`${filePath}.bak`, "utf-8")).toBe("original");
36
+ });
37
+ it("rotates backups on repeated overwrites", async () => {
38
+ const filePath = join(testDir, "test.md");
39
+ await writeFile(filePath, "v1", "utf-8");
40
+ await atomicWriteFile(filePath, "v2");
41
+ await atomicWriteFile(filePath, "v3");
42
+ await atomicWriteFile(filePath, "v4");
43
+ expect(await readFile(filePath, "utf-8")).toBe("v4");
44
+ expect(await readFile(`${filePath}.bak`, "utf-8")).toBe("v3");
45
+ expect(await readFile(`${filePath}.bak.1`, "utf-8")).toBe("v2");
46
+ expect(await readFile(`${filePath}.bak.2`, "utf-8")).toBe("v1");
47
+ });
48
+ it("does not create backup when backup=false", async () => {
49
+ const filePath = join(testDir, "test.md");
50
+ await writeFile(filePath, "original", "utf-8");
51
+ await atomicWriteFile(filePath, "updated", { backup: false });
52
+ expect(await readFile(filePath, "utf-8")).toBe("updated");
53
+ expect(existsSync(`${filePath}.bak`)).toBe(false);
54
+ });
55
+ it("does not leave temp files on success", async () => {
56
+ const filePath = join(testDir, "test.md");
57
+ await atomicWriteFile(filePath, "content");
58
+ const files = await readdir(testDir);
59
+ expect(files.filter((f) => f.includes(".tmp"))).toHaveLength(0);
60
+ });
61
+ it("respects maxBackups option", async () => {
62
+ const filePath = join(testDir, "test.md");
63
+ await writeFile(filePath, "v1", "utf-8");
64
+ for (let i = 2; i <= 6; i++) {
65
+ await atomicWriteFile(filePath, `v${i}`, { maxBackups: 2 });
66
+ }
67
+ // Should only keep 2 backups: .bak and .bak.1
68
+ expect(existsSync(`${filePath}.bak`)).toBe(true);
69
+ expect(existsSync(`${filePath}.bak.1`)).toBe(true);
70
+ // .bak.2 should not exist (maxBackups=2 means slots: .bak, .bak.1)
71
+ });
72
+ });
73
+ // ─────────────────────────────────────────────────────────────────────────────
74
+ // withFileLock
75
+ // ─────────────────────────────────────────────────────────────────────────────
76
+ describe("withFileLock", () => {
77
+ it("acquires lock and runs callback", async () => {
78
+ const filePath = join(testDir, "data.md");
79
+ const result = await withFileLock(filePath, async () => {
80
+ return 42;
81
+ });
82
+ expect(result).toBe(42);
83
+ });
84
+ it("cleans up lock file after callback", async () => {
85
+ const filePath = join(testDir, "data.md");
86
+ await withFileLock(filePath, async () => { });
87
+ const files = await readdir(testDir);
88
+ expect(files.filter((f) => f.includes(".lock"))).toHaveLength(0);
89
+ });
90
+ it("cleans up lock file after callback throws", async () => {
91
+ const filePath = join(testDir, "data.md");
92
+ await expect(withFileLock(filePath, async () => {
93
+ throw new Error("test error");
94
+ })).rejects.toThrow("test error");
95
+ const files = await readdir(testDir);
96
+ expect(files.filter((f) => f.includes(".lock"))).toHaveLength(0);
97
+ });
98
+ it("serializes concurrent access", async () => {
99
+ const filePath = join(testDir, "counter.txt");
100
+ await writeFile(filePath, "0", "utf-8");
101
+ // Run 5 concurrent increments
102
+ const results = await Promise.all(Array.from({ length: 5 }, () => withFileLock(filePath, async () => {
103
+ const val = parseInt(await readFile(filePath, "utf-8"), 10);
104
+ await writeFile(filePath, String(val + 1), "utf-8");
105
+ return val + 1;
106
+ })));
107
+ // All increments should have serialized correctly
108
+ const finalValue = await readFile(filePath, "utf-8");
109
+ expect(parseInt(finalValue, 10)).toBe(5);
110
+ // Results should contain all values 1-5
111
+ expect(results.sort()).toEqual([1, 2, 3, 4, 5]);
112
+ });
113
+ it("supports nested lock counting", async () => {
114
+ const filePath = join(testDir, "data.md");
115
+ const result = await withFileLock(filePath, async () => {
116
+ // Nested lock on same file should not deadlock
117
+ return withFileLock(filePath, async () => {
118
+ return "nested ok";
119
+ });
120
+ });
121
+ expect(result).toBe("nested ok");
122
+ });
123
+ it("times out if lock cannot be acquired", async () => {
124
+ const filePath = join(testDir, "data.md");
125
+ const lockPath = `${filePath}.lock`;
126
+ // Create a lock file from a "live" process (our own PID)
127
+ await mkdir(testDir, { recursive: true });
128
+ await writeFile(lockPath, JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }), "utf-8");
129
+ // Should timeout since lock is held by a "live" process
130
+ await expect(withFileLock(filePath, async () => { }, { timeout: 200 })).rejects.toThrow(/timeout/i);
131
+ // Clean up the lock file manually
132
+ await rm(lockPath, { force: true });
133
+ });
134
+ it("detects stale locks from dead process", async () => {
135
+ const filePath = join(testDir, "data.md");
136
+ const lockPath = `${filePath}.lock`;
137
+ // Create a lock from a definitely-dead PID
138
+ await mkdir(testDir, { recursive: true });
139
+ await writeFile(lockPath, JSON.stringify({ pid: 999999999, createdAt: new Date().toISOString() }), "utf-8");
140
+ // Should succeed because the PID is dead
141
+ const result = await withFileLock(filePath, async () => "recovered");
142
+ expect(result).toBe("recovered");
143
+ });
144
+ it("detects stale locks by age", async () => {
145
+ const filePath = join(testDir, "data.md");
146
+ const lockPath = `${filePath}.lock`;
147
+ // Create a lock from our own PID but with an old timestamp
148
+ await mkdir(testDir, { recursive: true });
149
+ await writeFile(lockPath, JSON.stringify({
150
+ pid: process.pid,
151
+ createdAt: new Date(Date.now() - 2_000_000).toISOString(),
152
+ }), "utf-8");
153
+ // Should succeed because the lock is stale (older than 30min default)
154
+ const result = await withFileLock(filePath, async () => "recovered");
155
+ expect(result).toBe("recovered");
156
+ });
157
+ });
158
+ // ─────────────────────────────────────────────────────────────────────────────
159
+ // safeWriteJson
160
+ // ─────────────────────────────────────────────────────────────────────────────
161
+ describe("safeWriteJson", () => {
162
+ it("writes valid JSON with formatting", async () => {
163
+ const filePath = join(testDir, "data.json");
164
+ await safeWriteJson(filePath, { key: "value", num: 42 });
165
+ const raw = await readFile(filePath, "utf-8");
166
+ expect(raw).toBe('{\n "key": "value",\n "num": 42\n}\n');
167
+ });
168
+ it("creates directories recursively", async () => {
169
+ const filePath = join(testDir, "a", "b", "data.json");
170
+ await safeWriteJson(filePath, { ok: true });
171
+ const data = JSON.parse(await readFile(filePath, "utf-8"));
172
+ expect(data).toEqual({ ok: true });
173
+ });
174
+ });
175
+ // ─────────────────────────────────────────────────────────────────────────────
176
+ // loadJsonFile
177
+ // ─────────────────────────────────────────────────────────────────────────────
178
+ describe("loadJsonFile", () => {
179
+ it("loads valid JSON", async () => {
180
+ const filePath = join(testDir, "data.json");
181
+ await writeFile(filePath, '{"key": "value"}', "utf-8");
182
+ const data = await loadJsonFile(filePath);
183
+ expect(data).toEqual({ key: "value" });
184
+ });
185
+ it("returns undefined for missing file", async () => {
186
+ const data = await loadJsonFile(join(testDir, "nonexistent.json"));
187
+ expect(data).toBeUndefined();
188
+ });
189
+ it("returns undefined for corrupt JSON", async () => {
190
+ const filePath = join(testDir, "corrupt.json");
191
+ await writeFile(filePath, "not valid json {{{", "utf-8");
192
+ const data = await loadJsonFile(filePath);
193
+ expect(data).toBeUndefined();
194
+ });
195
+ });
196
+ //# sourceMappingURL=safe-io.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-io.test.js","sourceRoot":"","sources":["../src/safe-io.test.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,42 @@
1
+ /** Valid task statuses */
2
+ export declare const VALID_TASK_STATUSES: readonly ["pending", "in-progress", "blocked", "review", "completed", "cancelled"];
3
+ /** Valid project statuses */
4
+ export declare const VALID_PROJECT_STATUSES: readonly ["draft", "active", "paused", "completed", "archived"];
5
+ /** Valid provenance actions */
6
+ export declare const VALID_PROVENANCE_ACTIONS: readonly ["created", "updated", "merged"];
7
+ /** Valid priorities */
8
+ export declare const VALID_PRIORITIES: readonly ["low", "medium", "high", "critical"];
9
+ /**
10
+ * Normalize a task status, accepting common variants and typos.
11
+ * Returns the canonical status or null if unrecognizable.
12
+ *
13
+ * @example
14
+ * normalizeTaskStatus("in_progress") // => "in-progress"
15
+ * normalizeTaskStatus("WIP") // => "in-progress"
16
+ * normalizeTaskStatus("garbage") // => null
17
+ */
18
+ export declare function normalizeTaskStatus(status: string): string | null;
19
+ /**
20
+ * Normalize a provenance action, accepting common variants.
21
+ * Returns the canonical action or null if unrecognizable.
22
+ *
23
+ * @example
24
+ * normalizeProvenanceAction("drafted") // => "created"
25
+ * normalizeProvenanceAction("edit") // => "updated"
26
+ */
27
+ export declare function normalizeProvenanceAction(action: string): string | null;
28
+ /**
29
+ * Suggest the closest valid value for a given input.
30
+ * Returns the suggestion and a helpful error message.
31
+ *
32
+ * @example
33
+ * suggestValidValue("in_progress", VALID_TASK_STATUSES)
34
+ * // => { suggestion: "in-progress", message: "Did you mean 'in-progress'?" }
35
+ */
36
+ export declare function suggestValidValue(input: string, validValues: readonly string[]): {
37
+ suggestion: string | null;
38
+ message: string;
39
+ };
1
40
  /**
2
41
  * Validate an artifact or profile slug.
3
42
  * Slugs must be lowercase alphanumeric with hyphens, not starting with hyphen.
@@ -28,6 +67,7 @@ export declare function validatePath(root: string, targetPath: string): string;
28
67
  *
29
68
  * @param dateStr - The date string to validate
30
69
  * @returns true if valid, false otherwise
70
+ * @internal Utility function for future validation use
31
71
  */
32
72
  export declare function isValidDate(dateStr: string): boolean;
33
73
  /**
@@ -35,6 +75,7 @@ export declare function isValidDate(dateStr: string): boolean;
35
75
  *
36
76
  * @param timestamp - The timestamp string to validate
37
77
  * @returns true if valid, false otherwise
78
+ * @internal Utility function for future validation use
38
79
  */
39
80
  export declare function isValidTimestamp(timestamp: string): boolean;
40
81
  //# sourceMappingURL=validation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWjD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAUrE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAMpD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAG3D"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAcA,0BAA0B;AAC1B,eAAO,MAAM,mBAAmB,oFAOtB,CAAC;AAEX,6BAA6B;AAC7B,eAAO,MAAM,sBAAsB,iEAMzB,CAAC;AAEX,+BAA+B;AAC/B,eAAO,MAAM,wBAAwB,2CAA4C,CAAC;AAElF,uBAAuB;AACvB,eAAO,MAAM,gBAAgB,gDAAiD,CAAC;AA+B/E;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAcjE;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAcvE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA6BhD;AAmCD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWjD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAUrE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAMpD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAG3D"}
@@ -3,6 +3,162 @@ import { resolve, relative, isAbsolute } from "node:path";
3
3
  const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
4
4
  /** Maximum slug length */
5
5
  const MAX_SLUG_LENGTH = 100;
6
+ // ============================================================================
7
+ // Agent-Friendly Normalization
8
+ // ============================================================================
9
+ // These functions help agents avoid common format errors by auto-correcting
10
+ // typos and accepting common variants. This makes AWP more "plug and play".
11
+ /** Valid task statuses */
12
+ export const VALID_TASK_STATUSES = [
13
+ "pending",
14
+ "in-progress",
15
+ "blocked",
16
+ "review",
17
+ "completed",
18
+ "cancelled",
19
+ ];
20
+ /** Valid project statuses */
21
+ export const VALID_PROJECT_STATUSES = [
22
+ "draft",
23
+ "active",
24
+ "paused",
25
+ "completed",
26
+ "archived",
27
+ ];
28
+ /** Valid provenance actions */
29
+ export const VALID_PROVENANCE_ACTIONS = ["created", "updated", "merged"];
30
+ /** Valid priorities */
31
+ export const VALID_PRIORITIES = ["low", "medium", "high", "critical"];
32
+ /** Common typo mappings for task status */
33
+ const TASK_STATUS_ALIASES = {
34
+ // Underscore variants
35
+ in_progress: "in-progress",
36
+ in_review: "review",
37
+ // CamelCase variants
38
+ inProgress: "in-progress",
39
+ inReview: "review",
40
+ // Other common typos
41
+ "in progress": "in-progress",
42
+ done: "completed",
43
+ todo: "pending",
44
+ wip: "in-progress",
45
+ complete: "completed",
46
+ cancelled: "cancelled",
47
+ canceled: "cancelled",
48
+ };
49
+ /** Common typo mappings for provenance actions */
50
+ const PROVENANCE_ACTION_ALIASES = {
51
+ drafted: "created",
52
+ create: "created",
53
+ update: "updated",
54
+ edit: "updated",
55
+ edited: "updated",
56
+ modified: "updated",
57
+ merge: "merged",
58
+ };
59
+ /**
60
+ * Normalize a task status, accepting common variants and typos.
61
+ * Returns the canonical status or null if unrecognizable.
62
+ *
63
+ * @example
64
+ * normalizeTaskStatus("in_progress") // => "in-progress"
65
+ * normalizeTaskStatus("WIP") // => "in-progress"
66
+ * normalizeTaskStatus("garbage") // => null
67
+ */
68
+ export function normalizeTaskStatus(status) {
69
+ const lower = status.toLowerCase().trim();
70
+ // Direct match
71
+ if (VALID_TASK_STATUSES.includes(lower)) {
72
+ return lower;
73
+ }
74
+ // Alias match
75
+ if (TASK_STATUS_ALIASES[lower]) {
76
+ return TASK_STATUS_ALIASES[lower];
77
+ }
78
+ return null;
79
+ }
80
+ /**
81
+ * Normalize a provenance action, accepting common variants.
82
+ * Returns the canonical action or null if unrecognizable.
83
+ *
84
+ * @example
85
+ * normalizeProvenanceAction("drafted") // => "created"
86
+ * normalizeProvenanceAction("edit") // => "updated"
87
+ */
88
+ export function normalizeProvenanceAction(action) {
89
+ const lower = action.toLowerCase().trim();
90
+ // Direct match
91
+ if (VALID_PROVENANCE_ACTIONS.includes(lower)) {
92
+ return lower;
93
+ }
94
+ // Alias match
95
+ if (PROVENANCE_ACTION_ALIASES[lower]) {
96
+ return PROVENANCE_ACTION_ALIASES[lower];
97
+ }
98
+ return null;
99
+ }
100
+ /**
101
+ * Suggest the closest valid value for a given input.
102
+ * Returns the suggestion and a helpful error message.
103
+ *
104
+ * @example
105
+ * suggestValidValue("in_progress", VALID_TASK_STATUSES)
106
+ * // => { suggestion: "in-progress", message: "Did you mean 'in-progress'?" }
107
+ */
108
+ export function suggestValidValue(input, validValues) {
109
+ const lower = input.toLowerCase().trim();
110
+ // Check for underscore/camelCase variants
111
+ const hyphenated = lower
112
+ .replace(/_/g, "-")
113
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
114
+ .toLowerCase();
115
+ if (validValues.includes(hyphenated)) {
116
+ return {
117
+ suggestion: hyphenated,
118
+ message: `Did you mean '${hyphenated}'? (Use hyphens, not underscores)`,
119
+ };
120
+ }
121
+ // Simple Levenshtein-like check for close matches
122
+ for (const valid of validValues) {
123
+ if (levenshteinDistance(lower, valid) <= 2) {
124
+ return {
125
+ suggestion: valid,
126
+ message: `Did you mean '${valid}'?`,
127
+ };
128
+ }
129
+ }
130
+ return {
131
+ suggestion: null,
132
+ message: `Valid values: ${validValues.join(", ")}`,
133
+ };
134
+ }
135
+ /**
136
+ * Simple Levenshtein distance for typo detection.
137
+ */
138
+ function levenshteinDistance(a, b) {
139
+ if (a.length === 0)
140
+ return b.length;
141
+ if (b.length === 0)
142
+ return a.length;
143
+ const matrix = [];
144
+ for (let i = 0; i <= b.length; i++) {
145
+ matrix[i] = [i];
146
+ }
147
+ for (let j = 0; j <= a.length; j++) {
148
+ matrix[0][j] = j;
149
+ }
150
+ for (let i = 1; i <= b.length; i++) {
151
+ for (let j = 1; j <= a.length; j++) {
152
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
153
+ matrix[i][j] = matrix[i - 1][j - 1];
154
+ }
155
+ else {
156
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
157
+ }
158
+ }
159
+ }
160
+ return matrix[b.length][a.length];
161
+ }
6
162
  /**
7
163
  * Validate an artifact or profile slug.
8
164
  * Slugs must be lowercase alphanumeric with hyphens, not starting with hyphen.
@@ -52,6 +208,7 @@ export function validatePath(root, targetPath) {
52
208
  *
53
209
  * @param dateStr - The date string to validate
54
210
  * @returns true if valid, false otherwise
211
+ * @internal Utility function for future validation use
55
212
  */
56
213
  export function isValidDate(dateStr) {
57
214
  if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
@@ -65,6 +222,7 @@ export function isValidDate(dateStr) {
65
222
  *
66
223
  * @param timestamp - The timestamp string to validate
67
224
  * @returns true if valid, false otherwise
225
+ * @internal Utility function for future validation use
68
226
  */
69
227
  export function isValidTimestamp(timestamp) {
70
228
  const date = new Date(timestamp);
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE1D,8FAA8F;AAC9F,MAAM,YAAY,GAAG,sBAAsB,CAAC;AAE5C,0BAA0B;AAC1B,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,eAAe,CAAC;AACnE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC1C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,kBAAkB,IAAI,2EAA2E,CAClG,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,sBAAsB,eAAe,aAAa,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,UAAkB;IAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAEvC,8BAA8B;IAC9B,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,4BAA4B,UAAU,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;IACjC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-workspace/utils",
3
- "version": "0.7.0",
3
+ "version": "0.8.3",
4
4
  "description": "Shared utilities for AWP packages — validation, reputation calculations, and frontmatter parsing",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  "prepublishOnly": "npm run build"
42
42
  },
43
43
  "dependencies": {
44
- "@agent-workspace/core": "0.7.0",
44
+ "@agent-workspace/core": "0.8.2",
45
45
  "gray-matter": "^4.0.3"
46
46
  },
47
47
  "devDependencies": {