@codedrifters/configulator 0.0.301 → 0.0.302

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/lib/index.js CHANGED
@@ -246,6 +246,10 @@ __export(index_exports, {
246
246
  DEFAULT_STATE_FILE_PATH: () => DEFAULT_STATE_FILE_PATH,
247
247
  DEFAULT_STATUS_LABELS: () => DEFAULT_STATUS_LABELS,
248
248
  DEFAULT_TEARDOWN_BRANCH_PATTERNS: () => DEFAULT_TEARDOWN_BRANCH_PATTERNS,
249
+ DEFAULT_TEMPORAL_FRAMING_CADENCES: () => DEFAULT_TEMPORAL_FRAMING_CADENCES,
250
+ DEFAULT_TEMPORAL_FRAMING_EMIT_CHECKER: () => DEFAULT_TEMPORAL_FRAMING_EMIT_CHECKER,
251
+ DEFAULT_TEMPORAL_FRAMING_ENABLED: () => DEFAULT_TEMPORAL_FRAMING_ENABLED,
252
+ DEFAULT_TEMPORAL_FRAMING_PATHS: () => DEFAULT_TEMPORAL_FRAMING_PATHS,
249
253
  DEFAULT_TYPE_LABELS: () => DEFAULT_TYPE_LABELS,
250
254
  DEFAULT_UNBLOCK_COMMENT_TEMPLATE: () => DEFAULT_UNBLOCK_COMMENT_TEMPLATE,
251
255
  DEFAULT_UNBLOCK_DEPENDENTS_ENABLED: () => DEFAULT_UNBLOCK_DEPENDENTS_ENABLED,
@@ -277,6 +281,7 @@ __export(index_exports, {
277
281
  SUPPRESSED_WORKFLOW_RULE_NAMES: () => SUPPRESSED_WORKFLOW_RULE_NAMES,
278
282
  SampleLang: () => SampleLang,
279
283
  StarlightProject: () => StarlightProject,
284
+ TEMPORAL_FRAMING_CATEGORY_VALUES: () => TEMPORAL_FRAMING_CATEGORY_VALUES,
280
285
  TIER_AWARE_BUNDLE_NAMES: () => TIER_AWARE_BUNDLE_NAMES,
281
286
  TestRunner: () => TestRunner,
282
287
  TsDocCoverageKind: () => TsDocCoverageKind,
@@ -400,6 +405,8 @@ __export(index_exports, {
400
405
  renderSkillEvalsRunnerScript: () => renderSkillEvalsRunnerScript,
401
406
  renderSourceTierExamples: () => renderSourceTierExamples,
402
407
  renderStubIndexConventionRuleContent: () => renderStubIndexConventionRuleContent,
408
+ renderTemporalFramingCheckerScript: () => renderTemporalFramingCheckerScript,
409
+ renderTemporalFramingRuleContent: () => renderTemporalFramingRuleContent,
403
410
  renderUnblockDependentsScript: () => renderUnblockDependentsScript,
404
411
  renderUnblockDependentsSection: () => renderUnblockDependentsSection,
405
412
  requirementsAnalystBundle: () => requirementsAnalystBundle,
@@ -425,6 +432,7 @@ __export(index_exports, {
425
432
  resolveSharedEditing: () => resolveSharedEditing,
426
433
  resolveSkillEvals: () => resolveSkillEvals,
427
434
  resolveTemplateVariables: () => resolveTemplateVariables,
435
+ resolveTemporalFraming: () => resolveTemporalFraming,
428
436
  resolveTypeScriptProjectOutdir: () => resolveTypeScriptProjectOutdir,
429
437
  resolveUnblockDependents: () => resolveUnblockDependents,
430
438
  runScan: () => runScan,
@@ -446,6 +454,7 @@ __export(index_exports, {
446
454
  validateSharedEditingConfig: () => validateSharedEditingConfig,
447
455
  validateSkillEvalsConfig: () => validateSkillEvalsConfig,
448
456
  validateStarlightSingleton: () => validateStarlightSingleton,
457
+ validateTemporalFramingConfig: () => validateTemporalFramingConfig,
449
458
  validateUnblockDependentsConfig: () => validateUnblockDependentsConfig,
450
459
  vitestBundle: () => vitestBundle
451
460
  });
@@ -3177,6 +3186,457 @@ function renderStubIndexConventionRuleContent() {
3177
3186
  ].join("\n");
3178
3187
  }
3179
3188
 
3189
+ // src/agent/bundles/temporal-framing.ts
3190
+ var DEFAULT_TEMPORAL_FRAMING_ENABLED = true;
3191
+ var DEFAULT_TEMPORAL_FRAMING_PATHS = [
3192
+ "docs/src/content/docs/profiles/**/*.md",
3193
+ "docs/src/content/docs/industry-research/**/*.md",
3194
+ "docs/src/content/docs/software-research/**/*.md",
3195
+ "docs/src/content/docs/regulatory-research/**/*.md",
3196
+ "docs/src/content/docs/standards-research/**/*.md",
3197
+ "docs/src/content/docs/customer-research/**/*.md"
3198
+ ];
3199
+ var TEMPORAL_FRAMING_CATEGORY_VALUES = [
3200
+ "ownership",
3201
+ "company-leadership",
3202
+ "regulatory-status",
3203
+ "litigation",
3204
+ "dated-metrics"
3205
+ ];
3206
+ var DEFAULT_TEMPORAL_FRAMING_CADENCES = {
3207
+ ownership: 180,
3208
+ "company-leadership": 90,
3209
+ "regulatory-status": 30,
3210
+ litigation: 30,
3211
+ "dated-metrics": 180
3212
+ };
3213
+ var DEFAULT_TEMPORAL_FRAMING_EMIT_CHECKER = false;
3214
+ function resolveTemporalFraming(config) {
3215
+ const paths = config?.paths ?? DEFAULT_TEMPORAL_FRAMING_PATHS;
3216
+ assertValidPaths(paths);
3217
+ const cadences = resolveCadences(config?.cadences);
3218
+ return {
3219
+ enabled: config?.enabled ?? DEFAULT_TEMPORAL_FRAMING_ENABLED,
3220
+ paths,
3221
+ cadences,
3222
+ emitChecker: config?.emitChecker ?? DEFAULT_TEMPORAL_FRAMING_EMIT_CHECKER
3223
+ };
3224
+ }
3225
+ function validateTemporalFramingConfig(config) {
3226
+ return resolveTemporalFraming(config);
3227
+ }
3228
+ function renderTemporalFramingRuleContent(tf) {
3229
+ if (!tf.enabled) {
3230
+ return [
3231
+ "# Temporal Framing Convention",
3232
+ "",
3233
+ "**Temporal framing is not enforced in this project.** Agents are",
3234
+ "not required to add `as of [date]` qualifiers to time-sensitive",
3235
+ "claims, and refresh passes do not use a mechanical grep to find",
3236
+ "stale framing. Staleness is caught by review and downstream user",
3237
+ "reports alone.",
3238
+ "",
3239
+ "Enable the convention via",
3240
+ "`AgentConfigOptions.temporalFraming.enabled = true`."
3241
+ ].join("\n");
3242
+ }
3243
+ const lines = [
3244
+ "# Temporal Framing Convention",
3245
+ "",
3246
+ "Every factual claim that can change over time MUST carry an inline",
3247
+ "`as of [YYYY-MM-DD]` or `as of [Month YYYY]` qualifier on the",
3248
+ "**first** occurrence of the claim in the document. Once qualified",
3249
+ "once, subsequent references to the same fact in the same document",
3250
+ "do not need to repeat the qualifier \u2014 the first qualifier governs",
3251
+ "the document's snapshot date for that fact.",
3252
+ "",
3253
+ "The qualifier is mechanical, not editorial. Refresh agents grep",
3254
+ "for `as of ` to find the time-bounded claims; without the",
3255
+ "qualifier, a refresh pass has no cheap way to tell stale framing",
3256
+ "from stable facts.",
3257
+ "",
3258
+ "## What counts as time-sensitive",
3259
+ "",
3260
+ "A claim is time-sensitive (and must carry the qualifier) when it",
3261
+ "describes a state that **a single public event could invalidate",
3262
+ "within the document's expected refresh cadence**. Five recurring",
3263
+ "categories cover the common cases:",
3264
+ "",
3265
+ "| Category | Examples |",
3266
+ "|----------|----------|",
3267
+ '| **Ownership and investors** | "co-owned by X, Y, Z"; "majority-owned by"; "minority stake held by"; cap table; current investor lists |',
3268
+ '| **Leadership tenure** | "X is CEO"; "X chairs the committee"; "X serves as"; current board chair; current executive team |',
3269
+ '| **Regulatory or rule status** | "is in force"; "under public consultation"; "pending finalization"; "currently enjoined"; "under active investigation" |',
3270
+ '| **Litigation and enforcement** | "is under active scrutiny"; "investigation is ongoing"; "appeal is pending"; "consent decree remains in effect" |',
3271
+ '| **Pricing, valuation, and metrics** | "valued at $XB"; "annual revenue ~$X"; "processes N transactions"; "serves N customers" \u2014 when the figure is sourced from a dated press release or filing |',
3272
+ "",
3273
+ "A claim is **not** time-sensitive (and does not need the qualifier)",
3274
+ "when it describes a historical event, a statutory text, or a",
3275
+ "structural fact that does not change without amendment. Examples",
3276
+ "that are safe without a qualifier:",
3277
+ "",
3278
+ '- "Acquired by Advent International in May 2019" (a dated historical event \u2014 the date itself is the anchor)',
3279
+ '- "21 CFR Part 11 establishes criteria for electronic records" (statutory text)',
3280
+ '- "Headquartered in Dallas, Texas" (changes only on a documented move)',
3281
+ '- "Founded in 1999" (a one-time event)',
3282
+ "",
3283
+ "## Format",
3284
+ "",
3285
+ "Two acceptable forms:",
3286
+ "",
3287
+ "```markdown",
3288
+ "As of May 2026, Acme is owned by ...",
3289
+ "... Acme's CEO (as of May 2026) is ...",
3290
+ "```",
3291
+ "",
3292
+ "Use the granularity that matches the source citation:",
3293
+ "",
3294
+ "- `as of YYYY-MM-DD` when the source is a specific filing, press release, or court order with a precise date.",
3295
+ "- `as of Month YYYY` when the source is a self-reported page (e.g., a company's `About` page) where only the month-of-snapshot is meaningful.",
3296
+ "- Do **not** write `as of 2026` (year-only) \u2014 that's too coarse to drive a refresh decision.",
3297
+ "",
3298
+ "## Examples",
3299
+ "",
3300
+ "### Wrong \u2014 present-tense framing of a state that can change",
3301
+ "",
3302
+ "```markdown",
3303
+ "Availity is co-owned by major Blue Cross Blue Shield plans",
3304
+ "(HCSC, Elevance Health/Anthem, BCBS Minnesota), Francisco",
3305
+ "Partners, Novo Holdings, and Prettybrook Partners.",
3306
+ "```",
3307
+ "",
3308
+ "This claim might have been true at a specific date but goes stale",
3309
+ "as soon as any one investor exits. A refresh agent has no",
3310
+ "mechanical signal that the claim is time-sensitive.",
3311
+ "",
3312
+ "### Right \u2014 explicit qualifier on the first claim",
3313
+ "",
3314
+ "```markdown",
3315
+ "As of May 2026, Availity is co-owned by a mix of health plan",
3316
+ "strategic investors (HCSC, Elevance Health/Anthem, BCBS",
3317
+ "Minnesota, Humana, GuideWell) and financial sponsors (Novo",
3318
+ "Holdings, Prettybrook Partners).",
3319
+ "```",
3320
+ "",
3321
+ "The `as of May 2026` anchors the document's snapshot for the",
3322
+ "ownership claim. Subsequent references to Availity's ownership in",
3323
+ "the same document inherit that snapshot date.",
3324
+ "",
3325
+ "## When the convention applies",
3326
+ "",
3327
+ "The rule applies to every Markdown document any agent writes under",
3328
+ "the following path globs:",
3329
+ "",
3330
+ ...tf.paths.map((p) => `- \`${p}\``),
3331
+ "",
3332
+ "It does **not** apply to:",
3333
+ "",
3334
+ "- Meeting notes (`meetings/**`) \u2014 the meeting date itself is the implicit `as of` anchor.",
3335
+ "- Requirement documents (`requirements/**`) \u2014 formal requirements are versioned and dated via their own frontmatter.",
3336
+ "- The project context page (`project-context.md`) \u2014 maintained as a living snapshot under direct human review.",
3337
+ "",
3338
+ "## Refresh-agent behaviour",
3339
+ "",
3340
+ "A refresh pass on any covered document MUST:",
3341
+ "",
3342
+ "1. Grep for `as of ` (case-insensitive) in the document.",
3343
+ "2. For each qualifier, compute `today - <qualifier-date>` in days.",
3344
+ "3. Classify each qualified claim against the category-specific",
3345
+ " cadence table below.",
3346
+ "4. Re-verify every claim whose qualifier is older than its cadence.",
3347
+ "5. Update the qualifier to today's date on every re-verified claim",
3348
+ " that remains accurate.",
3349
+ "6. Treat documents that omit qualifiers entirely as needing",
3350
+ " **back-fill** before any other verification is attempted \u2014 the",
3351
+ " absence of qualifiers is itself a refresh finding.",
3352
+ "",
3353
+ "### Refresh cadence table",
3354
+ "",
3355
+ "Each category carries its own refresh cadence in days. Faster-decay",
3356
+ "claims (regulatory status, litigation) refresh more often;",
3357
+ "slower-decay claims (ownership, dated metrics) refresh less often.",
3358
+ "",
3359
+ "| Category | Cadence (days) |",
3360
+ "|----------|----------------|",
3361
+ `| \`ownership\` | ${tf.cadences.ownership} |`,
3362
+ `| \`company-leadership\` | ${tf.cadences["company-leadership"]} |`,
3363
+ `| \`regulatory-status\` | ${tf.cadences["regulatory-status"]} |`,
3364
+ `| \`litigation\` | ${tf.cadences.litigation} |`,
3365
+ `| \`dated-metrics\` | ${tf.cadences["dated-metrics"]} |`,
3366
+ "",
3367
+ "Consumers may override any subset of cadences via",
3368
+ "`AgentConfigOptions.temporalFraming.cadences`. Unspecified",
3369
+ "categories fall through to the defaults above."
3370
+ ];
3371
+ if (tf.emitChecker) {
3372
+ lines.push(
3373
+ "",
3374
+ "## Lint",
3375
+ "",
3376
+ "The bundled `.claude/procedures/check-temporal-framing.sh` helper",
3377
+ "enforces the rule mechanically. Given a set of changed files (as",
3378
+ "positional arguments or a newline-separated list on stdin), it",
3379
+ "fails non-zero when any file matches a configured path glob and",
3380
+ "carries time-sensitive framing without an `as of ` qualifier.",
3381
+ "",
3382
+ "```bash",
3383
+ "# Pre-commit (staged files):",
3384
+ "git diff --cached --name-only \\",
3385
+ " | .claude/procedures/check-temporal-framing.sh",
3386
+ "",
3387
+ "# CI (branch-vs-base):",
3388
+ "git diff --name-only origin/main...HEAD \\",
3389
+ " | .claude/procedures/check-temporal-framing.sh",
3390
+ "```",
3391
+ "",
3392
+ "The lint is a coarse signal \u2014 it flags files that look",
3393
+ "time-sensitive but carry no qualifier. Authors and reviewers are",
3394
+ "still expected to validate that each qualifier is correctly",
3395
+ "anchored to its source citation."
3396
+ );
3397
+ }
3398
+ lines.push(
3399
+ "",
3400
+ "## Out of scope",
3401
+ "",
3402
+ "- Auto-generating qualifiers from source-citation dates. Authors",
3403
+ " read the source and write the qualifier explicitly.",
3404
+ "- Replacing existing dated frontmatter conventions. Frontmatter",
3405
+ " `date:` fields anchor the document's overall snapshot; the",
3406
+ " inline qualifier anchors individual time-sensitive claims so a",
3407
+ " refresh pass can re-verify them without re-reading the whole",
3408
+ " document.",
3409
+ "- Back-filling pre-existing documents in bulk. Refresh passes",
3410
+ " back-fill qualifiers on the documents they touch; bulk",
3411
+ " back-fill is a downstream consumer cleanup task, not a",
3412
+ " configulator change."
3413
+ );
3414
+ return lines.join("\n");
3415
+ }
3416
+ function renderTemporalFramingCheckerScript(tf) {
3417
+ const patternsLiteral = tf.paths.map((p) => ` ${JSON.stringify(p)}`).join("\n");
3418
+ return [
3419
+ "#!/usr/bin/env bash",
3420
+ "# check-temporal-framing.sh \u2014 Enforce the temporal-framing convention.",
3421
+ "#",
3422
+ "# Usage:",
3423
+ "# .claude/procedures/check-temporal-framing.sh <file>...",
3424
+ "# git diff --name-only HEAD | .claude/procedures/check-temporal-framing.sh",
3425
+ "#",
3426
+ "# Fails non-zero when any changed file matches a configured path",
3427
+ "# pattern AND contains present-tense framing of a time-sensitive",
3428
+ "# claim category AND does NOT carry an `as of ` qualifier anywhere",
3429
+ "# in the file. The lint is file-level, not line-level \u2014 the cheap",
3430
+ "# coarse signal is enough to catch the failure mode the convention",
3431
+ "# targets, and a finer-grained check would carry too much false",
3432
+ "# positive cost.",
3433
+ "#",
3434
+ "# The path patterns are rendered from the consumer's",
3435
+ "# TemporalFramingConfig at synth time \u2014 do not edit by hand;",
3436
+ "# regenerate via `pnpm exec projen`.",
3437
+ "#",
3438
+ "# Patterns support the `**` recursive-segment wildcard even though",
3439
+ "# bash's native `[[ $file == $glob ]]` pattern matching does not:",
3440
+ "# each glob is converted to a POSIX-extended regex internally and",
3441
+ "# matched with `=~`, so the script works uniformly on bash 3.2",
3442
+ "# (macOS default) and bash 4+ (Linux / CI).",
3443
+ "",
3444
+ "set -uo pipefail",
3445
+ "",
3446
+ "err() {",
3447
+ ' printf "check-temporal-framing.sh: %s\\n" "$*" >&2',
3448
+ "}",
3449
+ "",
3450
+ "# Path glob patterns. Translated to anchored POSIX-extended regexes",
3451
+ "# at runtime by `glob_to_regex` below so `**` matches recursively",
3452
+ "# across path segments.",
3453
+ "path_patterns=(",
3454
+ patternsLiteral,
3455
+ ")",
3456
+ "",
3457
+ "# Triggers \u2014 present-tense framing keywords that indicate a",
3458
+ "# time-sensitive claim category. Match is case-insensitive and",
3459
+ "# whole-word; the keyword list is deliberately small (one or two",
3460
+ "# canonical phrases per category) to keep false positives low.",
3461
+ "triggers=(",
3462
+ ' "is owned by"',
3463
+ ' "co-owned by"',
3464
+ ' "majority-owned by"',
3465
+ ' "minority-owned by"',
3466
+ ' "is co-owned"',
3467
+ ' "is currently"',
3468
+ ' "currently chairs"',
3469
+ ' "currently serves"',
3470
+ ' "is in force"',
3471
+ ' "under public consultation"',
3472
+ ' "under active investigation"',
3473
+ ' "is pending"',
3474
+ ' "investigation is ongoing"',
3475
+ ")",
3476
+ "",
3477
+ "# Convert a bash-style glob into an anchored POSIX-extended regex:",
3478
+ "# **/ \u2192 (.+/)? (zero or more path segments plus trailing slash)",
3479
+ "# ** \u2192 .* (anywhere, including `/`)",
3480
+ "# * \u2192 [^/]* (single path segment)",
3481
+ "# ? \u2192 [^/] (single non-slash char)",
3482
+ "glob_to_regex() {",
3483
+ ' local glob="$1"',
3484
+ ' local regex=""',
3485
+ " local i=0",
3486
+ " local len=${#glob}",
3487
+ " while (( i < len )); do",
3488
+ ' local c="${glob:i:1}"',
3489
+ ' if [[ "$c" == "*" ]]; then',
3490
+ ' local next="${glob:i+1:1}"',
3491
+ ' if [[ "$next" == "*" ]]; then',
3492
+ ' local after="${glob:i+2:1}"',
3493
+ ' if [[ "$after" == "/" ]]; then',
3494
+ ' regex+="(.+/)?"',
3495
+ " i=$((i+3))",
3496
+ " continue",
3497
+ " fi",
3498
+ ' regex+=".*"',
3499
+ " i=$((i+2))",
3500
+ " continue",
3501
+ " fi",
3502
+ ' regex+="[^/]*"',
3503
+ " i=$((i+1))",
3504
+ " continue",
3505
+ " fi",
3506
+ ' if [[ "$c" == "?" ]]; then',
3507
+ ' regex+="[^/]"',
3508
+ " i=$((i+1))",
3509
+ " continue",
3510
+ " fi",
3511
+ ' case "$c" in',
3512
+ " .|+|\\(|\\)|\\[|\\]|\\{|\\}|\\||\\^|\\$|\\\\)",
3513
+ ' regex+="\\\\$c"',
3514
+ " ;;",
3515
+ " *)",
3516
+ ' regex+="$c"',
3517
+ " ;;",
3518
+ " esac",
3519
+ " i=$((i+1))",
3520
+ " done",
3521
+ ' printf "^%s$" "$regex"',
3522
+ "}",
3523
+ "",
3524
+ "# Check whether $file matches any of the configured path patterns.",
3525
+ "# Returns 0 on match, 1 otherwise.",
3526
+ "matches_any_pattern() {",
3527
+ ' local file="$1"',
3528
+ " local pattern regex",
3529
+ ' for pattern in "${path_patterns[@]}"; do',
3530
+ ' regex=$(glob_to_regex "$pattern")',
3531
+ ' if [[ "$file" =~ $regex ]]; then',
3532
+ " return 0",
3533
+ " fi",
3534
+ " done",
3535
+ " return 1",
3536
+ "}",
3537
+ "",
3538
+ "# Check whether $file contains any present-tense framing trigger",
3539
+ "# without an `as of ` qualifier anywhere in the file. Returns 0",
3540
+ "# when the file is OK (no triggers, or triggers + qualifier), 1",
3541
+ "# when the file is malformed (triggers + no qualifier).",
3542
+ "file_ok() {",
3543
+ ' local file="$1"',
3544
+ ' [[ ! -f "$file" ]] && return 0',
3545
+ " local found_trigger=0",
3546
+ " local trigger",
3547
+ ' for trigger in "${triggers[@]}"; do',
3548
+ ' if grep -qi -- "$trigger" "$file"; then',
3549
+ " found_trigger=1",
3550
+ " break",
3551
+ " fi",
3552
+ " done",
3553
+ " if [[ $found_trigger -eq 0 ]]; then",
3554
+ " return 0",
3555
+ " fi",
3556
+ ' if grep -qi -- "as of " "$file"; then',
3557
+ " return 0",
3558
+ " fi",
3559
+ " return 1",
3560
+ "}",
3561
+ "",
3562
+ "# Read the file list from positional args or stdin.",
3563
+ "files=()",
3564
+ "if [[ $# -gt 0 ]]; then",
3565
+ ' files=("$@")',
3566
+ "else",
3567
+ " while IFS= read -r line; do",
3568
+ ' [[ -n "$line" ]] && files+=("$line")',
3569
+ " done",
3570
+ "fi",
3571
+ "",
3572
+ "if [[ ${#files[@]} -eq 0 ]]; then",
3573
+ " exit 0",
3574
+ "fi",
3575
+ "",
3576
+ "violations=0",
3577
+ 'for file in "${files[@]}"; do',
3578
+ ' if ! matches_any_pattern "$file"; then',
3579
+ " continue",
3580
+ " fi",
3581
+ ' if ! file_ok "$file"; then',
3582
+ ' err "$file: contains time-sensitive framing without an \\"as of\\" qualifier"',
3583
+ " violations=$((violations+1))",
3584
+ " fi",
3585
+ "done",
3586
+ "",
3587
+ "if [[ $violations -gt 0 ]]; then",
3588
+ ' err "found $violations file(s) violating the temporal-framing convention"',
3589
+ " exit 1",
3590
+ "fi",
3591
+ "exit 0"
3592
+ ].join("\n");
3593
+ }
3594
+ function assertValidPaths(paths) {
3595
+ if (!Array.isArray(paths)) {
3596
+ throw new Error(
3597
+ `TemporalFramingConfig.paths must be an array; got ${typeof paths}`
3598
+ );
3599
+ }
3600
+ for (let i = 0; i < paths.length; i++) {
3601
+ const entry = paths[i];
3602
+ if (typeof entry !== "string" || entry.trim().length === 0) {
3603
+ throw new Error(
3604
+ `TemporalFramingConfig.paths[${i}] must be a non-empty string; got ${JSON.stringify(
3605
+ entry
3606
+ )}`
3607
+ );
3608
+ }
3609
+ }
3610
+ }
3611
+ function resolveCadences(supplied) {
3612
+ const merged = {
3613
+ ...DEFAULT_TEMPORAL_FRAMING_CADENCES
3614
+ };
3615
+ if (supplied !== void 0) {
3616
+ for (const category of TEMPORAL_FRAMING_CATEGORY_VALUES) {
3617
+ const value = supplied[category];
3618
+ if (value === void 0) {
3619
+ continue;
3620
+ }
3621
+ assertValidCadence(value, category);
3622
+ merged[category] = value;
3623
+ }
3624
+ }
3625
+ return merged;
3626
+ }
3627
+ function assertValidCadence(value, field) {
3628
+ if (typeof value !== "number" || !Number.isInteger(value)) {
3629
+ throw new Error(
3630
+ `TemporalFramingConfig.cadences.${field} must be a positive integer; got ${value}`
3631
+ );
3632
+ }
3633
+ if (value <= 0) {
3634
+ throw new Error(
3635
+ `TemporalFramingConfig.cadences.${field} must be a positive integer; got ${value}`
3636
+ );
3637
+ }
3638
+ }
3639
+
3180
3640
  // src/agent/bundles/base.ts
3181
3641
  var createPackageSkill = {
3182
3642
  name: "create-package",
@@ -4286,6 +4746,16 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
4286
4746
  },
4287
4747
  tags: ["workflow"]
4288
4748
  },
4749
+ {
4750
+ name: "temporal-framing-convention",
4751
+ description: "Temporal-framing convention: every agent-authored time-sensitive factual claim (ownership, leadership tenure, regulatory status, litigation, dated metrics) carries an inline `as of [YYYY-MM-DD]` or `as of [Month YYYY]` qualifier on first occurrence so refresh agents have a mechanical signal for which claims to re-verify.",
4752
+ scope: AGENT_RULE_SCOPE.ALWAYS,
4753
+ content: renderTemporalFramingRuleContent(resolveTemporalFraming()),
4754
+ platforms: {
4755
+ cursor: { exclude: true }
4756
+ },
4757
+ tags: ["workflow"]
4758
+ },
4289
4759
  {
4290
4760
  name: "skill-evals",
4291
4761
  description: "Skill eval harness contract: declarative prompt/expected-output regression suites per skill at `<skillsRoot>/<skill-name>/evals/evals.json`, parameterised by a shared product-context fixture so the same eval shape works across every configulator-consuming project without forking fixtures.",
@@ -6529,19 +6999,29 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
6529
6999
  " - <source URL> \u2014 <date accessed>",
6530
7000
  " ```",
6531
7001
  "",
6532
- "4. **Decide whether a follow-up issue is warranted.** Create a",
7002
+ "4. **Apply the `temporal-framing-convention` rule.** Every",
7003
+ " time-sensitive factual claim in the profile \u2014 ownership,",
7004
+ " leadership tenure, regulatory status, litigation, dated metrics",
7005
+ " \u2014 must carry an inline `as of [YYYY-MM-DD]` or `as of [Month",
7006
+ " YYYY]` qualifier on its **first** occurrence in the document.",
7007
+ " Use the granularity that matches the source citation (precise",
7008
+ " date for a filing or press release; month for a self-reported",
7009
+ " `About` page). The qualifier is mechanical, not editorial \u2014 it",
7010
+ " is the signal Phase 5 (Refresh) uses to grep for stale claims.",
7011
+ "",
7012
+ "5. **Decide whether a follow-up issue is warranted.** Create a",
6533
7013
  " `company:followup` issue (depending on this draft issue) only if",
6534
7014
  " the profile lists at least one follow-up candidate. Otherwise,",
6535
7015
  " note in the draft issue's closing comment that no follow-up is",
6536
7016
  " needed.",
6537
7017
  "",
6538
- "5. **File a `company:match` issue** (depending on this draft issue)",
7018
+ "6. **File a `company:match` issue** (depending on this draft issue)",
6539
7019
  " so the profile gets enriched against the project's business-model",
6540
7020
  " canvases. Skip the match issue only when the invoking project has",
6541
7021
  " no business-model canvases at all (Phase 4 will detect and exit",
6542
7022
  " gracefully in that case).",
6543
7023
  "",
6544
- "6. **Commit and push** the profile file. Close the draft issue.",
7024
+ "7. **Commit and push** the profile file. Close the draft issue.",
6545
7025
  "",
6546
7026
  "---",
6547
7027
  "",
@@ -6780,26 +7260,51 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
6780
7260
  " the issue body does not set `force: true`, close the issue with",
6781
7261
  " a short comment and stop \u2014 do not burn the search budget.",
6782
7262
  "",
6783
- "3. **Run 4\u20136 targeted searches.** Focus on:",
7263
+ "3. **Grep for `as of ` qualifiers.** Per the",
7264
+ " `temporal-framing-convention` rule, every time-sensitive claim in",
7265
+ " the profile carries an inline `as of [YYYY-MM-DD]` or `as of",
7266
+ " [Month YYYY]` qualifier. Build the list of qualified claims and",
7267
+ " compute the age (in days) of each one against today's date.",
7268
+ " Classify each claim against the cadence table in the",
7269
+ " `temporal-framing-convention` rule:",
7270
+ " - **company-leadership** \u2014 90 days",
7271
+ " - **regulatory-status** \u2014 30 days",
7272
+ " - **litigation** \u2014 30 days",
7273
+ " - **ownership** \u2014 180 days",
7274
+ " - **dated-metrics** \u2014 180 days",
7275
+ "",
7276
+ " Treat every qualifier older than its category's cadence as a",
7277
+ " re-verification target for the targeted-search step below. If the",
7278
+ " profile contains **no** `as of ` qualifiers at all, treat the",
7279
+ " entire profile as needing **back-fill** \u2014 apply the convention to",
7280
+ " every time-sensitive claim before running the targeted searches.",
7281
+ "",
7282
+ "4. **Run 4\u20136 targeted searches.** Focus on the categories surfaced by",
7283
+ " the qualifier grep (step 3), plus any additional signals worth a",
7284
+ " refresh:",
6784
7285
  " - `<company name>` + recent news, press releases, or blog posts",
6785
7286
  " - Product launches, feature changes, or pricing updates",
6786
7287
  " - Funding rounds, acquisitions, or material ownership changes",
6787
7288
  " - Leadership changes (CEO, CTO, CPO, notable departures/arrivals)",
6788
7289
  " - New partnerships, certifications, or regulatory milestones",
6789
7290
  "",
6790
- "4. **Update the profile in place.** Edit the affected sections with",
7291
+ "5. **Update the profile in place.** Edit the affected sections with",
6791
7292
  " the new information. Cite every new claim. Preserve the slug and",
6792
7293
  " the original `parent_issue` field. Bump the `date` frontmatter to",
6793
- " today's date.",
7294
+ " today's date. **Update the `as of` qualifier** on every claim you",
7295
+ " re-verified \u2014 change the date to today's date when the claim",
7296
+ " still holds; rewrite the framing when the claim has changed.",
7297
+ " Surface unverifiable or stale-and-changed claims through",
7298
+ " `## Risks / Open Questions` rather than silently dropping them.",
6794
7299
  "",
6795
- "5. **Do not silently re-type the company.** If the refresh surfaces",
7300
+ "6. **Do not silently re-type the company.** If the refresh surfaces",
6796
7301
  " evidence that the company's primary type has changed (e.g., an",
6797
7302
  " industry-player that now sells a directly competing product),",
6798
7303
  " flag it in `## Risks / Open Questions` and stop \u2014 do **not**",
6799
7304
  " rewrite the `company_type` frontmatter without an explicit",
6800
7305
  " override in the refresh issue body (`retype: <new type>`).",
6801
7306
  "",
6802
- "6. **Append a revision-history row.** Summarize the delta in one",
7307
+ "7. **Append a revision-history row.** Summarize the delta in one",
6803
7308
  " line:",
6804
7309
  "",
6805
7310
  " ```markdown",
@@ -6814,14 +7319,14 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
6814
7319
  " | YYYY-MM-DD | Refreshed: no material change |",
6815
7320
  " ```",
6816
7321
  "",
6817
- "7. **Update reference entries** (if the project tracks company",
7322
+ "8. **Update reference entries** (if the project tracks company",
6818
7323
  " references under research directories). If the company's",
6819
7324
  " relevance to a research area has shifted \u2014 for example, it is no",
6820
7325
  " longer a live competitor, or it now plays in a new segment \u2014",
6821
7326
  " update the corresponding entry under `referencedIn` and the",
6822
7327
  " research-area doc that links to this profile.",
6823
7328
  "",
6824
- "8. **Commit and push.** Close the refresh issue with a short comment",
7329
+ "9. **Commit and push.** Close the refresh issue with a short comment",
6825
7330
  " summarizing the delta (or `no material change`).",
6826
7331
  "",
6827
7332
  "---",
@@ -15750,6 +16255,17 @@ function buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier) {
15750
16255
  "full background, and do not rewrite the profile body for facts that",
15751
16256
  "have not changed.",
15752
16257
  "",
16258
+ "**Temporal framing.** Refreshes follow the same grep-`as of `-and-",
16259
+ "re-verify pattern documented for `company:refresh` in the",
16260
+ "`company-profile-analyst` bundle (Phase 5, steps 3 and 5): build",
16261
+ "the list of qualified claims, classify each against the cadence",
16262
+ "table in the `temporal-framing-convention` rule (90d for current",
16263
+ "role / employer / tenure, falling back to 180d for slower-decay",
16264
+ "claims), re-verify every claim past cadence, and update the",
16265
+ "qualifier date on every claim that still holds. Treat a profile",
16266
+ "with no `as of ` qualifiers as needing back-fill before any other",
16267
+ "verification.",
16268
+ "",
15753
16269
  "### Steps",
15754
16270
  "",
15755
16271
  "1. **Read the existing profile** at the path referenced in the issue",
@@ -29452,6 +29968,17 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
29452
29968
  });
29453
29969
  }
29454
29970
  }
29971
+ const resolvedTemporalFraming = validateTemporalFramingConfig(
29972
+ this.options.temporalFraming
29973
+ );
29974
+ if (resolvedTemporalFraming.enabled && resolvedTemporalFraming.emitChecker) {
29975
+ new import_projen8.TextFile(this, ".claude/procedures/check-temporal-framing.sh", {
29976
+ lines: renderTemporalFramingCheckerScript(
29977
+ resolvedTemporalFraming
29978
+ ).split("\n"),
29979
+ executable: true
29980
+ });
29981
+ }
29455
29982
  const platforms = this.resolvePlatforms();
29456
29983
  const rules = this.resolveRules();
29457
29984
  const skills = this.resolveSkills();
@@ -29799,6 +30326,23 @@ ${hook}`
29799
30326
  });
29800
30327
  }
29801
30328
  }
30329
+ const resolvedTemporalFramingForRules = resolveTemporalFraming(
30330
+ this.options.temporalFraming
30331
+ );
30332
+ if (this.options.temporalFraming) {
30333
+ const temporalFramingRule = ruleMap.get("temporal-framing-convention");
30334
+ if (temporalFramingRule) {
30335
+ const temporalFramingContent = renderTemporalFramingRuleContent(
30336
+ resolvedTemporalFramingForRules
30337
+ );
30338
+ if (temporalFramingContent !== temporalFramingRule.content) {
30339
+ ruleMap.set("temporal-framing-convention", {
30340
+ ...temporalFramingRule,
30341
+ content: temporalFramingContent
30342
+ });
30343
+ }
30344
+ }
30345
+ }
29802
30346
  const tierExamples = this.options.features?.sourceTierExamples;
29803
30347
  if (_AgentConfig.hasActiveTierExamples(tierExamples)) {
29804
30348
  const sourceRule = ruleMap.get("source-quality-verification");
@@ -34567,6 +35111,10 @@ var TypeScriptConfig = class extends import_projen24.Component {
34567
35111
  DEFAULT_STATE_FILE_PATH,
34568
35112
  DEFAULT_STATUS_LABELS,
34569
35113
  DEFAULT_TEARDOWN_BRANCH_PATTERNS,
35114
+ DEFAULT_TEMPORAL_FRAMING_CADENCES,
35115
+ DEFAULT_TEMPORAL_FRAMING_EMIT_CHECKER,
35116
+ DEFAULT_TEMPORAL_FRAMING_ENABLED,
35117
+ DEFAULT_TEMPORAL_FRAMING_PATHS,
34570
35118
  DEFAULT_TYPE_LABELS,
34571
35119
  DEFAULT_UNBLOCK_COMMENT_TEMPLATE,
34572
35120
  DEFAULT_UNBLOCK_DEPENDENTS_ENABLED,
@@ -34598,6 +35146,7 @@ var TypeScriptConfig = class extends import_projen24.Component {
34598
35146
  SUPPRESSED_WORKFLOW_RULE_NAMES,
34599
35147
  SampleLang,
34600
35148
  StarlightProject,
35149
+ TEMPORAL_FRAMING_CATEGORY_VALUES,
34601
35150
  TIER_AWARE_BUNDLE_NAMES,
34602
35151
  TestRunner,
34603
35152
  TsDocCoverageKind,
@@ -34721,6 +35270,8 @@ var TypeScriptConfig = class extends import_projen24.Component {
34721
35270
  renderSkillEvalsRunnerScript,
34722
35271
  renderSourceTierExamples,
34723
35272
  renderStubIndexConventionRuleContent,
35273
+ renderTemporalFramingCheckerScript,
35274
+ renderTemporalFramingRuleContent,
34724
35275
  renderUnblockDependentsScript,
34725
35276
  renderUnblockDependentsSection,
34726
35277
  requirementsAnalystBundle,
@@ -34746,6 +35297,7 @@ var TypeScriptConfig = class extends import_projen24.Component {
34746
35297
  resolveSharedEditing,
34747
35298
  resolveSkillEvals,
34748
35299
  resolveTemplateVariables,
35300
+ resolveTemporalFraming,
34749
35301
  resolveTypeScriptProjectOutdir,
34750
35302
  resolveUnblockDependents,
34751
35303
  runScan,
@@ -34767,6 +35319,7 @@ var TypeScriptConfig = class extends import_projen24.Component {
34767
35319
  validateSharedEditingConfig,
34768
35320
  validateSkillEvalsConfig,
34769
35321
  validateStarlightSingleton,
35322
+ validateTemporalFramingConfig,
34770
35323
  validateUnblockDependentsConfig,
34771
35324
  vitestBundle
34772
35325
  });