@codedrifters/configulator 0.0.301 → 0.0.303

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.mjs CHANGED
@@ -2898,6 +2898,457 @@ function renderStubIndexConventionRuleContent() {
2898
2898
  ].join("\n");
2899
2899
  }
2900
2900
 
2901
+ // src/agent/bundles/temporal-framing.ts
2902
+ var DEFAULT_TEMPORAL_FRAMING_ENABLED = true;
2903
+ var DEFAULT_TEMPORAL_FRAMING_PATHS = [
2904
+ "docs/src/content/docs/profiles/**/*.md",
2905
+ "docs/src/content/docs/industry-research/**/*.md",
2906
+ "docs/src/content/docs/software-research/**/*.md",
2907
+ "docs/src/content/docs/regulatory-research/**/*.md",
2908
+ "docs/src/content/docs/standards-research/**/*.md",
2909
+ "docs/src/content/docs/customer-research/**/*.md"
2910
+ ];
2911
+ var TEMPORAL_FRAMING_CATEGORY_VALUES = [
2912
+ "ownership",
2913
+ "company-leadership",
2914
+ "regulatory-status",
2915
+ "litigation",
2916
+ "dated-metrics"
2917
+ ];
2918
+ var DEFAULT_TEMPORAL_FRAMING_CADENCES = {
2919
+ ownership: 180,
2920
+ "company-leadership": 90,
2921
+ "regulatory-status": 30,
2922
+ litigation: 30,
2923
+ "dated-metrics": 180
2924
+ };
2925
+ var DEFAULT_TEMPORAL_FRAMING_EMIT_CHECKER = false;
2926
+ function resolveTemporalFraming(config) {
2927
+ const paths = config?.paths ?? DEFAULT_TEMPORAL_FRAMING_PATHS;
2928
+ assertValidPaths(paths);
2929
+ const cadences = resolveCadences(config?.cadences);
2930
+ return {
2931
+ enabled: config?.enabled ?? DEFAULT_TEMPORAL_FRAMING_ENABLED,
2932
+ paths,
2933
+ cadences,
2934
+ emitChecker: config?.emitChecker ?? DEFAULT_TEMPORAL_FRAMING_EMIT_CHECKER
2935
+ };
2936
+ }
2937
+ function validateTemporalFramingConfig(config) {
2938
+ return resolveTemporalFraming(config);
2939
+ }
2940
+ function renderTemporalFramingRuleContent(tf) {
2941
+ if (!tf.enabled) {
2942
+ return [
2943
+ "# Temporal Framing Convention",
2944
+ "",
2945
+ "**Temporal framing is not enforced in this project.** Agents are",
2946
+ "not required to add `as of [date]` qualifiers to time-sensitive",
2947
+ "claims, and refresh passes do not use a mechanical grep to find",
2948
+ "stale framing. Staleness is caught by review and downstream user",
2949
+ "reports alone.",
2950
+ "",
2951
+ "Enable the convention via",
2952
+ "`AgentConfigOptions.temporalFraming.enabled = true`."
2953
+ ].join("\n");
2954
+ }
2955
+ const lines = [
2956
+ "# Temporal Framing Convention",
2957
+ "",
2958
+ "Every factual claim that can change over time MUST carry an inline",
2959
+ "`as of [YYYY-MM-DD]` or `as of [Month YYYY]` qualifier on the",
2960
+ "**first** occurrence of the claim in the document. Once qualified",
2961
+ "once, subsequent references to the same fact in the same document",
2962
+ "do not need to repeat the qualifier \u2014 the first qualifier governs",
2963
+ "the document's snapshot date for that fact.",
2964
+ "",
2965
+ "The qualifier is mechanical, not editorial. Refresh agents grep",
2966
+ "for `as of ` to find the time-bounded claims; without the",
2967
+ "qualifier, a refresh pass has no cheap way to tell stale framing",
2968
+ "from stable facts.",
2969
+ "",
2970
+ "## What counts as time-sensitive",
2971
+ "",
2972
+ "A claim is time-sensitive (and must carry the qualifier) when it",
2973
+ "describes a state that **a single public event could invalidate",
2974
+ "within the document's expected refresh cadence**. Five recurring",
2975
+ "categories cover the common cases:",
2976
+ "",
2977
+ "| Category | Examples |",
2978
+ "|----------|----------|",
2979
+ '| **Ownership and investors** | "co-owned by X, Y, Z"; "majority-owned by"; "minority stake held by"; cap table; current investor lists |',
2980
+ '| **Leadership tenure** | "X is CEO"; "X chairs the committee"; "X serves as"; current board chair; current executive team |',
2981
+ '| **Regulatory or rule status** | "is in force"; "under public consultation"; "pending finalization"; "currently enjoined"; "under active investigation" |',
2982
+ '| **Litigation and enforcement** | "is under active scrutiny"; "investigation is ongoing"; "appeal is pending"; "consent decree remains in effect" |',
2983
+ '| **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 |',
2984
+ "",
2985
+ "A claim is **not** time-sensitive (and does not need the qualifier)",
2986
+ "when it describes a historical event, a statutory text, or a",
2987
+ "structural fact that does not change without amendment. Examples",
2988
+ "that are safe without a qualifier:",
2989
+ "",
2990
+ '- "Acquired by Advent International in May 2019" (a dated historical event \u2014 the date itself is the anchor)',
2991
+ '- "21 CFR Part 11 establishes criteria for electronic records" (statutory text)',
2992
+ '- "Headquartered in Dallas, Texas" (changes only on a documented move)',
2993
+ '- "Founded in 1999" (a one-time event)',
2994
+ "",
2995
+ "## Format",
2996
+ "",
2997
+ "Two acceptable forms:",
2998
+ "",
2999
+ "```markdown",
3000
+ "As of May 2026, Acme is owned by ...",
3001
+ "... Acme's CEO (as of May 2026) is ...",
3002
+ "```",
3003
+ "",
3004
+ "Use the granularity that matches the source citation:",
3005
+ "",
3006
+ "- `as of YYYY-MM-DD` when the source is a specific filing, press release, or court order with a precise date.",
3007
+ "- `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.",
3008
+ "- Do **not** write `as of 2026` (year-only) \u2014 that's too coarse to drive a refresh decision.",
3009
+ "",
3010
+ "## Examples",
3011
+ "",
3012
+ "### Wrong \u2014 present-tense framing of a state that can change",
3013
+ "",
3014
+ "```markdown",
3015
+ "Availity is co-owned by major Blue Cross Blue Shield plans",
3016
+ "(HCSC, Elevance Health/Anthem, BCBS Minnesota), Francisco",
3017
+ "Partners, Novo Holdings, and Prettybrook Partners.",
3018
+ "```",
3019
+ "",
3020
+ "This claim might have been true at a specific date but goes stale",
3021
+ "as soon as any one investor exits. A refresh agent has no",
3022
+ "mechanical signal that the claim is time-sensitive.",
3023
+ "",
3024
+ "### Right \u2014 explicit qualifier on the first claim",
3025
+ "",
3026
+ "```markdown",
3027
+ "As of May 2026, Availity is co-owned by a mix of health plan",
3028
+ "strategic investors (HCSC, Elevance Health/Anthem, BCBS",
3029
+ "Minnesota, Humana, GuideWell) and financial sponsors (Novo",
3030
+ "Holdings, Prettybrook Partners).",
3031
+ "```",
3032
+ "",
3033
+ "The `as of May 2026` anchors the document's snapshot for the",
3034
+ "ownership claim. Subsequent references to Availity's ownership in",
3035
+ "the same document inherit that snapshot date.",
3036
+ "",
3037
+ "## When the convention applies",
3038
+ "",
3039
+ "The rule applies to every Markdown document any agent writes under",
3040
+ "the following path globs:",
3041
+ "",
3042
+ ...tf.paths.map((p) => `- \`${p}\``),
3043
+ "",
3044
+ "It does **not** apply to:",
3045
+ "",
3046
+ "- Meeting notes (`meetings/**`) \u2014 the meeting date itself is the implicit `as of` anchor.",
3047
+ "- Requirement documents (`requirements/**`) \u2014 formal requirements are versioned and dated via their own frontmatter.",
3048
+ "- The project context page (`project-context.md`) \u2014 maintained as a living snapshot under direct human review.",
3049
+ "",
3050
+ "## Refresh-agent behaviour",
3051
+ "",
3052
+ "A refresh pass on any covered document MUST:",
3053
+ "",
3054
+ "1. Grep for `as of ` (case-insensitive) in the document.",
3055
+ "2. For each qualifier, compute `today - <qualifier-date>` in days.",
3056
+ "3. Classify each qualified claim against the category-specific",
3057
+ " cadence table below.",
3058
+ "4. Re-verify every claim whose qualifier is older than its cadence.",
3059
+ "5. Update the qualifier to today's date on every re-verified claim",
3060
+ " that remains accurate.",
3061
+ "6. Treat documents that omit qualifiers entirely as needing",
3062
+ " **back-fill** before any other verification is attempted \u2014 the",
3063
+ " absence of qualifiers is itself a refresh finding.",
3064
+ "",
3065
+ "### Refresh cadence table",
3066
+ "",
3067
+ "Each category carries its own refresh cadence in days. Faster-decay",
3068
+ "claims (regulatory status, litigation) refresh more often;",
3069
+ "slower-decay claims (ownership, dated metrics) refresh less often.",
3070
+ "",
3071
+ "| Category | Cadence (days) |",
3072
+ "|----------|----------------|",
3073
+ `| \`ownership\` | ${tf.cadences.ownership} |`,
3074
+ `| \`company-leadership\` | ${tf.cadences["company-leadership"]} |`,
3075
+ `| \`regulatory-status\` | ${tf.cadences["regulatory-status"]} |`,
3076
+ `| \`litigation\` | ${tf.cadences.litigation} |`,
3077
+ `| \`dated-metrics\` | ${tf.cadences["dated-metrics"]} |`,
3078
+ "",
3079
+ "Consumers may override any subset of cadences via",
3080
+ "`AgentConfigOptions.temporalFraming.cadences`. Unspecified",
3081
+ "categories fall through to the defaults above."
3082
+ ];
3083
+ if (tf.emitChecker) {
3084
+ lines.push(
3085
+ "",
3086
+ "## Lint",
3087
+ "",
3088
+ "The bundled `.claude/procedures/check-temporal-framing.sh` helper",
3089
+ "enforces the rule mechanically. Given a set of changed files (as",
3090
+ "positional arguments or a newline-separated list on stdin), it",
3091
+ "fails non-zero when any file matches a configured path glob and",
3092
+ "carries time-sensitive framing without an `as of ` qualifier.",
3093
+ "",
3094
+ "```bash",
3095
+ "# Pre-commit (staged files):",
3096
+ "git diff --cached --name-only \\",
3097
+ " | .claude/procedures/check-temporal-framing.sh",
3098
+ "",
3099
+ "# CI (branch-vs-base):",
3100
+ "git diff --name-only origin/main...HEAD \\",
3101
+ " | .claude/procedures/check-temporal-framing.sh",
3102
+ "```",
3103
+ "",
3104
+ "The lint is a coarse signal \u2014 it flags files that look",
3105
+ "time-sensitive but carry no qualifier. Authors and reviewers are",
3106
+ "still expected to validate that each qualifier is correctly",
3107
+ "anchored to its source citation."
3108
+ );
3109
+ }
3110
+ lines.push(
3111
+ "",
3112
+ "## Out of scope",
3113
+ "",
3114
+ "- Auto-generating qualifiers from source-citation dates. Authors",
3115
+ " read the source and write the qualifier explicitly.",
3116
+ "- Replacing existing dated frontmatter conventions. Frontmatter",
3117
+ " `date:` fields anchor the document's overall snapshot; the",
3118
+ " inline qualifier anchors individual time-sensitive claims so a",
3119
+ " refresh pass can re-verify them without re-reading the whole",
3120
+ " document.",
3121
+ "- Back-filling pre-existing documents in bulk. Refresh passes",
3122
+ " back-fill qualifiers on the documents they touch; bulk",
3123
+ " back-fill is a downstream consumer cleanup task, not a",
3124
+ " configulator change."
3125
+ );
3126
+ return lines.join("\n");
3127
+ }
3128
+ function renderTemporalFramingCheckerScript(tf) {
3129
+ const patternsLiteral = tf.paths.map((p) => ` ${JSON.stringify(p)}`).join("\n");
3130
+ return [
3131
+ "#!/usr/bin/env bash",
3132
+ "# check-temporal-framing.sh \u2014 Enforce the temporal-framing convention.",
3133
+ "#",
3134
+ "# Usage:",
3135
+ "# .claude/procedures/check-temporal-framing.sh <file>...",
3136
+ "# git diff --name-only HEAD | .claude/procedures/check-temporal-framing.sh",
3137
+ "#",
3138
+ "# Fails non-zero when any changed file matches a configured path",
3139
+ "# pattern AND contains present-tense framing of a time-sensitive",
3140
+ "# claim category AND does NOT carry an `as of ` qualifier anywhere",
3141
+ "# in the file. The lint is file-level, not line-level \u2014 the cheap",
3142
+ "# coarse signal is enough to catch the failure mode the convention",
3143
+ "# targets, and a finer-grained check would carry too much false",
3144
+ "# positive cost.",
3145
+ "#",
3146
+ "# The path patterns are rendered from the consumer's",
3147
+ "# TemporalFramingConfig at synth time \u2014 do not edit by hand;",
3148
+ "# regenerate via `pnpm exec projen`.",
3149
+ "#",
3150
+ "# Patterns support the `**` recursive-segment wildcard even though",
3151
+ "# bash's native `[[ $file == $glob ]]` pattern matching does not:",
3152
+ "# each glob is converted to a POSIX-extended regex internally and",
3153
+ "# matched with `=~`, so the script works uniformly on bash 3.2",
3154
+ "# (macOS default) and bash 4+ (Linux / CI).",
3155
+ "",
3156
+ "set -uo pipefail",
3157
+ "",
3158
+ "err() {",
3159
+ ' printf "check-temporal-framing.sh: %s\\n" "$*" >&2',
3160
+ "}",
3161
+ "",
3162
+ "# Path glob patterns. Translated to anchored POSIX-extended regexes",
3163
+ "# at runtime by `glob_to_regex` below so `**` matches recursively",
3164
+ "# across path segments.",
3165
+ "path_patterns=(",
3166
+ patternsLiteral,
3167
+ ")",
3168
+ "",
3169
+ "# Triggers \u2014 present-tense framing keywords that indicate a",
3170
+ "# time-sensitive claim category. Match is case-insensitive and",
3171
+ "# whole-word; the keyword list is deliberately small (one or two",
3172
+ "# canonical phrases per category) to keep false positives low.",
3173
+ "triggers=(",
3174
+ ' "is owned by"',
3175
+ ' "co-owned by"',
3176
+ ' "majority-owned by"',
3177
+ ' "minority-owned by"',
3178
+ ' "is co-owned"',
3179
+ ' "is currently"',
3180
+ ' "currently chairs"',
3181
+ ' "currently serves"',
3182
+ ' "is in force"',
3183
+ ' "under public consultation"',
3184
+ ' "under active investigation"',
3185
+ ' "is pending"',
3186
+ ' "investigation is ongoing"',
3187
+ ")",
3188
+ "",
3189
+ "# Convert a bash-style glob into an anchored POSIX-extended regex:",
3190
+ "# **/ \u2192 (.+/)? (zero or more path segments plus trailing slash)",
3191
+ "# ** \u2192 .* (anywhere, including `/`)",
3192
+ "# * \u2192 [^/]* (single path segment)",
3193
+ "# ? \u2192 [^/] (single non-slash char)",
3194
+ "glob_to_regex() {",
3195
+ ' local glob="$1"',
3196
+ ' local regex=""',
3197
+ " local i=0",
3198
+ " local len=${#glob}",
3199
+ " while (( i < len )); do",
3200
+ ' local c="${glob:i:1}"',
3201
+ ' if [[ "$c" == "*" ]]; then',
3202
+ ' local next="${glob:i+1:1}"',
3203
+ ' if [[ "$next" == "*" ]]; then',
3204
+ ' local after="${glob:i+2:1}"',
3205
+ ' if [[ "$after" == "/" ]]; then',
3206
+ ' regex+="(.+/)?"',
3207
+ " i=$((i+3))",
3208
+ " continue",
3209
+ " fi",
3210
+ ' regex+=".*"',
3211
+ " i=$((i+2))",
3212
+ " continue",
3213
+ " fi",
3214
+ ' regex+="[^/]*"',
3215
+ " i=$((i+1))",
3216
+ " continue",
3217
+ " fi",
3218
+ ' if [[ "$c" == "?" ]]; then',
3219
+ ' regex+="[^/]"',
3220
+ " i=$((i+1))",
3221
+ " continue",
3222
+ " fi",
3223
+ ' case "$c" in',
3224
+ " .|+|\\(|\\)|\\[|\\]|\\{|\\}|\\||\\^|\\$|\\\\)",
3225
+ ' regex+="\\\\$c"',
3226
+ " ;;",
3227
+ " *)",
3228
+ ' regex+="$c"',
3229
+ " ;;",
3230
+ " esac",
3231
+ " i=$((i+1))",
3232
+ " done",
3233
+ ' printf "^%s$" "$regex"',
3234
+ "}",
3235
+ "",
3236
+ "# Check whether $file matches any of the configured path patterns.",
3237
+ "# Returns 0 on match, 1 otherwise.",
3238
+ "matches_any_pattern() {",
3239
+ ' local file="$1"',
3240
+ " local pattern regex",
3241
+ ' for pattern in "${path_patterns[@]}"; do',
3242
+ ' regex=$(glob_to_regex "$pattern")',
3243
+ ' if [[ "$file" =~ $regex ]]; then',
3244
+ " return 0",
3245
+ " fi",
3246
+ " done",
3247
+ " return 1",
3248
+ "}",
3249
+ "",
3250
+ "# Check whether $file contains any present-tense framing trigger",
3251
+ "# without an `as of ` qualifier anywhere in the file. Returns 0",
3252
+ "# when the file is OK (no triggers, or triggers + qualifier), 1",
3253
+ "# when the file is malformed (triggers + no qualifier).",
3254
+ "file_ok() {",
3255
+ ' local file="$1"',
3256
+ ' [[ ! -f "$file" ]] && return 0',
3257
+ " local found_trigger=0",
3258
+ " local trigger",
3259
+ ' for trigger in "${triggers[@]}"; do',
3260
+ ' if grep -qi -- "$trigger" "$file"; then',
3261
+ " found_trigger=1",
3262
+ " break",
3263
+ " fi",
3264
+ " done",
3265
+ " if [[ $found_trigger -eq 0 ]]; then",
3266
+ " return 0",
3267
+ " fi",
3268
+ ' if grep -qi -- "as of " "$file"; then',
3269
+ " return 0",
3270
+ " fi",
3271
+ " return 1",
3272
+ "}",
3273
+ "",
3274
+ "# Read the file list from positional args or stdin.",
3275
+ "files=()",
3276
+ "if [[ $# -gt 0 ]]; then",
3277
+ ' files=("$@")',
3278
+ "else",
3279
+ " while IFS= read -r line; do",
3280
+ ' [[ -n "$line" ]] && files+=("$line")',
3281
+ " done",
3282
+ "fi",
3283
+ "",
3284
+ "if [[ ${#files[@]} -eq 0 ]]; then",
3285
+ " exit 0",
3286
+ "fi",
3287
+ "",
3288
+ "violations=0",
3289
+ 'for file in "${files[@]}"; do',
3290
+ ' if ! matches_any_pattern "$file"; then',
3291
+ " continue",
3292
+ " fi",
3293
+ ' if ! file_ok "$file"; then',
3294
+ ' err "$file: contains time-sensitive framing without an \\"as of\\" qualifier"',
3295
+ " violations=$((violations+1))",
3296
+ " fi",
3297
+ "done",
3298
+ "",
3299
+ "if [[ $violations -gt 0 ]]; then",
3300
+ ' err "found $violations file(s) violating the temporal-framing convention"',
3301
+ " exit 1",
3302
+ "fi",
3303
+ "exit 0"
3304
+ ].join("\n");
3305
+ }
3306
+ function assertValidPaths(paths) {
3307
+ if (!Array.isArray(paths)) {
3308
+ throw new Error(
3309
+ `TemporalFramingConfig.paths must be an array; got ${typeof paths}`
3310
+ );
3311
+ }
3312
+ for (let i = 0; i < paths.length; i++) {
3313
+ const entry = paths[i];
3314
+ if (typeof entry !== "string" || entry.trim().length === 0) {
3315
+ throw new Error(
3316
+ `TemporalFramingConfig.paths[${i}] must be a non-empty string; got ${JSON.stringify(
3317
+ entry
3318
+ )}`
3319
+ );
3320
+ }
3321
+ }
3322
+ }
3323
+ function resolveCadences(supplied) {
3324
+ const merged = {
3325
+ ...DEFAULT_TEMPORAL_FRAMING_CADENCES
3326
+ };
3327
+ if (supplied !== void 0) {
3328
+ for (const category of TEMPORAL_FRAMING_CATEGORY_VALUES) {
3329
+ const value = supplied[category];
3330
+ if (value === void 0) {
3331
+ continue;
3332
+ }
3333
+ assertValidCadence(value, category);
3334
+ merged[category] = value;
3335
+ }
3336
+ }
3337
+ return merged;
3338
+ }
3339
+ function assertValidCadence(value, field) {
3340
+ if (typeof value !== "number" || !Number.isInteger(value)) {
3341
+ throw new Error(
3342
+ `TemporalFramingConfig.cadences.${field} must be a positive integer; got ${value}`
3343
+ );
3344
+ }
3345
+ if (value <= 0) {
3346
+ throw new Error(
3347
+ `TemporalFramingConfig.cadences.${field} must be a positive integer; got ${value}`
3348
+ );
3349
+ }
3350
+ }
3351
+
2901
3352
  // src/agent/bundles/base.ts
2902
3353
  var createPackageSkill = {
2903
3354
  name: "create-package",
@@ -4007,6 +4458,16 @@ function buildBaseBundle(paths = DEFAULT_AGENT_PATHS) {
4007
4458
  },
4008
4459
  tags: ["workflow"]
4009
4460
  },
4461
+ {
4462
+ name: "temporal-framing-convention",
4463
+ 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.",
4464
+ scope: AGENT_RULE_SCOPE.ALWAYS,
4465
+ content: renderTemporalFramingRuleContent(resolveTemporalFraming()),
4466
+ platforms: {
4467
+ cursor: { exclude: true }
4468
+ },
4469
+ tags: ["workflow"]
4470
+ },
4010
4471
  {
4011
4472
  name: "skill-evals",
4012
4473
  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.",
@@ -6250,19 +6711,29 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
6250
6711
  " - <source URL> \u2014 <date accessed>",
6251
6712
  " ```",
6252
6713
  "",
6253
- "4. **Decide whether a follow-up issue is warranted.** Create a",
6714
+ "4. **Apply the `temporal-framing-convention` rule.** Every",
6715
+ " time-sensitive factual claim in the profile \u2014 ownership,",
6716
+ " leadership tenure, regulatory status, litigation, dated metrics",
6717
+ " \u2014 must carry an inline `as of [YYYY-MM-DD]` or `as of [Month",
6718
+ " YYYY]` qualifier on its **first** occurrence in the document.",
6719
+ " Use the granularity that matches the source citation (precise",
6720
+ " date for a filing or press release; month for a self-reported",
6721
+ " `About` page). The qualifier is mechanical, not editorial \u2014 it",
6722
+ " is the signal Phase 5 (Refresh) uses to grep for stale claims.",
6723
+ "",
6724
+ "5. **Decide whether a follow-up issue is warranted.** Create a",
6254
6725
  " `company:followup` issue (depending on this draft issue) only if",
6255
6726
  " the profile lists at least one follow-up candidate. Otherwise,",
6256
6727
  " note in the draft issue's closing comment that no follow-up is",
6257
6728
  " needed.",
6258
6729
  "",
6259
- "5. **File a `company:match` issue** (depending on this draft issue)",
6730
+ "6. **File a `company:match` issue** (depending on this draft issue)",
6260
6731
  " so the profile gets enriched against the project's business-model",
6261
6732
  " canvases. Skip the match issue only when the invoking project has",
6262
6733
  " no business-model canvases at all (Phase 4 will detect and exit",
6263
6734
  " gracefully in that case).",
6264
6735
  "",
6265
- "6. **Commit and push** the profile file. Close the draft issue.",
6736
+ "7. **Commit and push** the profile file. Close the draft issue.",
6266
6737
  "",
6267
6738
  "---",
6268
6739
  "",
@@ -6501,26 +6972,51 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
6501
6972
  " the issue body does not set `force: true`, close the issue with",
6502
6973
  " a short comment and stop \u2014 do not burn the search budget.",
6503
6974
  "",
6504
- "3. **Run 4\u20136 targeted searches.** Focus on:",
6975
+ "3. **Grep for `as of ` qualifiers.** Per the",
6976
+ " `temporal-framing-convention` rule, every time-sensitive claim in",
6977
+ " the profile carries an inline `as of [YYYY-MM-DD]` or `as of",
6978
+ " [Month YYYY]` qualifier. Build the list of qualified claims and",
6979
+ " compute the age (in days) of each one against today's date.",
6980
+ " Classify each claim against the cadence table in the",
6981
+ " `temporal-framing-convention` rule:",
6982
+ " - **company-leadership** \u2014 90 days",
6983
+ " - **regulatory-status** \u2014 30 days",
6984
+ " - **litigation** \u2014 30 days",
6985
+ " - **ownership** \u2014 180 days",
6986
+ " - **dated-metrics** \u2014 180 days",
6987
+ "",
6988
+ " Treat every qualifier older than its category's cadence as a",
6989
+ " re-verification target for the targeted-search step below. If the",
6990
+ " profile contains **no** `as of ` qualifiers at all, treat the",
6991
+ " entire profile as needing **back-fill** \u2014 apply the convention to",
6992
+ " every time-sensitive claim before running the targeted searches.",
6993
+ "",
6994
+ "4. **Run 4\u20136 targeted searches.** Focus on the categories surfaced by",
6995
+ " the qualifier grep (step 3), plus any additional signals worth a",
6996
+ " refresh:",
6505
6997
  " - `<company name>` + recent news, press releases, or blog posts",
6506
6998
  " - Product launches, feature changes, or pricing updates",
6507
6999
  " - Funding rounds, acquisitions, or material ownership changes",
6508
7000
  " - Leadership changes (CEO, CTO, CPO, notable departures/arrivals)",
6509
7001
  " - New partnerships, certifications, or regulatory milestones",
6510
7002
  "",
6511
- "4. **Update the profile in place.** Edit the affected sections with",
7003
+ "5. **Update the profile in place.** Edit the affected sections with",
6512
7004
  " the new information. Cite every new claim. Preserve the slug and",
6513
7005
  " the original `parent_issue` field. Bump the `date` frontmatter to",
6514
- " today's date.",
7006
+ " today's date. **Update the `as of` qualifier** on every claim you",
7007
+ " re-verified \u2014 change the date to today's date when the claim",
7008
+ " still holds; rewrite the framing when the claim has changed.",
7009
+ " Surface unverifiable or stale-and-changed claims through",
7010
+ " `## Risks / Open Questions` rather than silently dropping them.",
6515
7011
  "",
6516
- "5. **Do not silently re-type the company.** If the refresh surfaces",
7012
+ "6. **Do not silently re-type the company.** If the refresh surfaces",
6517
7013
  " evidence that the company's primary type has changed (e.g., an",
6518
7014
  " industry-player that now sells a directly competing product),",
6519
7015
  " flag it in `## Risks / Open Questions` and stop \u2014 do **not**",
6520
7016
  " rewrite the `company_type` frontmatter without an explicit",
6521
7017
  " override in the refresh issue body (`retype: <new type>`).",
6522
7018
  "",
6523
- "6. **Append a revision-history row.** Summarize the delta in one",
7019
+ "7. **Append a revision-history row.** Summarize the delta in one",
6524
7020
  " line:",
6525
7021
  "",
6526
7022
  " ```markdown",
@@ -6535,14 +7031,14 @@ function buildCompanyProfileAnalystSubAgent(paths, issueDefaults, tier) {
6535
7031
  " | YYYY-MM-DD | Refreshed: no material change |",
6536
7032
  " ```",
6537
7033
  "",
6538
- "7. **Update reference entries** (if the project tracks company",
7034
+ "8. **Update reference entries** (if the project tracks company",
6539
7035
  " references under research directories). If the company's",
6540
7036
  " relevance to a research area has shifted \u2014 for example, it is no",
6541
7037
  " longer a live competitor, or it now plays in a new segment \u2014",
6542
7038
  " update the corresponding entry under `referencedIn` and the",
6543
7039
  " research-area doc that links to this profile.",
6544
7040
  "",
6545
- "8. **Commit and push.** Close the refresh issue with a short comment",
7041
+ "9. **Commit and push.** Close the refresh issue with a short comment",
6546
7042
  " summarizing the delta (or `no material change`).",
6547
7043
  "",
6548
7044
  "---",
@@ -10712,14 +11208,17 @@ var maintenanceAuditBundle = buildMaintenanceAuditBundle();
10712
11208
  function buildMeetingAnalystSubAgent(tier) {
10713
11209
  return {
10714
11210
  name: "meeting-analyst",
10715
- description: "Processes meeting transcripts through a 4-phase pipeline: extract, notes, draft, and link",
11211
+ description: "Processes meeting notes and transcripts (sibling-tree layout) through a 4-phase pipeline: extract, notes, draft, and link",
10716
11212
  model: tier,
10717
11213
  maxTurns: 80,
10718
11214
  platforms: { cursor: { exclude: true } },
10719
11215
  prompt: [
10720
11216
  "# Meeting Analyst Agent",
10721
11217
  "",
10722
- "You process meeting transcripts through a structured 4-phase pipeline.",
11218
+ "You process meeting notes and transcripts through a structured",
11219
+ "4-phase pipeline. Notes and transcripts live in **parallel sibling",
11220
+ "trees** keyed by basename \u2014 see the **Meeting file layout** section",
11221
+ "below for the canonical directory layout and input-case matrix.",
10723
11222
  "Each phase runs as a **separate agent session**, triggered by its own",
10724
11223
  "GitHub issue with a `meeting:*` phase label. You handle exactly **one",
10725
11224
  "phase per session** \u2014 read the issue to determine which phase to execute.",
@@ -10752,6 +11251,87 @@ function buildMeetingAnalystSubAgent(tier) {
10752
11251
  " Apply the type-specific rules from the **Meeting type handling**",
10753
11252
  " table (phases 1\u20132) and the area-filtered routing rules from the",
10754
11253
  " **Areas filtering** table (phase 4).",
11254
+ "7. **Treat notes and transcripts as paired siblings.** Notes are the",
11255
+ " curated narrative summary; transcripts are the verbatim evidence",
11256
+ " record. Both are first-class inputs to Phase 1 \u2014 never assume the",
11257
+ " transcript is the sole input.",
11258
+ "",
11259
+ "---",
11260
+ "",
11261
+ "## Meeting file layout (sibling-tree model)",
11262
+ "",
11263
+ "The bundle organises meeting artifacts as **three parallel trees**",
11264
+ "rooted at `<meetingsRoot>`, partitioned by meeting type",
11265
+ "(`internal` / `external`):",
11266
+ "",
11267
+ "```",
11268
+ "<meetingsRoot>/",
11269
+ "\u251C\u2500\u2500 transcripts/{internal,external}/ \u2190 raw verbatim transcripts (input, evidence)",
11270
+ "\u251C\u2500\u2500 notes/{internal,external}/ \u2190 curated narrative summary (input + Phase 2 output)",
11271
+ "\u2514\u2500\u2500 insights/{internal,external}/ \u2190 structured extraction (Phase 1 output)",
11272
+ "```",
11273
+ "",
11274
+ "Within a given `{internal,external}` partition, the three trees share",
11275
+ "**identical basenames**: a meeting captured as",
11276
+ "`notes/internal/2026-03-14-sprint-kickoff.md` has its companion",
11277
+ "transcript at `transcripts/internal/2026-03-14-sprint-kickoff.md` and",
11278
+ "its Phase 1 extraction at `insights/internal/2026-03-14-sprint-kickoff.md`.",
11279
+ "Agents look up siblings via filesystem (basename match across the",
11280
+ "three trees), not via frontmatter cross-references.",
11281
+ "",
11282
+ "**Input-case matrix.** A meeting always has a `notes/` or",
11283
+ "`transcripts/` entry (often both); Phase 1 must handle every case:",
11284
+ "",
11285
+ "| Notes file? | Transcript file? | Case | Phase-1 behaviour |",
11286
+ "|-------------|------------------|------|--------------------|",
11287
+ "| Yes | Yes | **Both** | Read both; transcript is verbatim ground truth, notes are first-draft summary. Set `transcript_available: true`, `confidence: high`. |",
11288
+ "| Yes | No | **Notes-only** | Read notes only. Set `transcript_available: false`, `confidence: medium`. Do not invent dialogue the notes do not record. |",
11289
+ "| No | Yes | **Transcript-only** | Read transcript only. Set `transcript_available: true`, `confidence: high`. Phase 2 will author the notes file from the transcript. |",
11290
+ "",
11291
+ "Notes frontmatter is **optional**. A bare imported file with no",
11292
+ "frontmatter is a normal input case \u2014 treat missing fields as the",
11293
+ "defaults `meeting_type: other`, `source: unknown`, `confidence: medium`,",
11294
+ "and populate the real values on first processing. Never fail-loud on",
11295
+ "absent frontmatter.",
11296
+ "",
11297
+ "### Notes / insights frontmatter schema",
11298
+ "",
11299
+ "The following frontmatter fields are recognised on notes and",
11300
+ "insights files. Every field is optional \u2014 agents must read them",
11301
+ "defensively and supply defaults when absent.",
11302
+ "",
11303
+ "| Field | Type | Purpose | Default |",
11304
+ "|-------|------|---------|---------|",
11305
+ "| `slug` | string | Starlight slug override; lets agents normalise the URL without renaming files. | derived from filename |",
11306
+ "| `source` | `gemini` \\| `human` \\| `hybrid` \\| `unknown` | Provenance of the original notes (which authoring path produced them). | `unknown` |",
11307
+ "| `transcript_available` | boolean | Cached result of the filesystem check for the sibling transcript. | re-derive from filesystem |",
11308
+ "| `confidence` | `high` \\| `medium` \\| `low` | Reliability of the extraction. Downstream draft phases mark requirements **Tentative** when `confidence != high`. | `medium` |",
11309
+ "",
11310
+ "These additions coexist with the existing `meeting_type` and `areas`",
11311
+ "fields documented in the **Meeting type handling** and **Areas",
11312
+ "filtering** sections below.",
11313
+ "",
11314
+ "Transcript files themselves remain frontmatter-free \u2014 they are",
11315
+ "evidence-only, and agents never navigate to a transcript as a",
11316
+ "Starlight artifact.",
11317
+ "",
11318
+ "### Tree-level index pages",
11319
+ "",
11320
+ "Each of the three trees carries an `index.md` at its root and at",
11321
+ "each `{internal,external}` partition so Starlight renders a clean",
11322
+ "sidebar:",
11323
+ "",
11324
+ "- `<meetingsRoot>/transcripts/index.md` (and one per partition)",
11325
+ "- `<meetingsRoot>/notes/index.md` (and one per partition)",
11326
+ "- `<meetingsRoot>/insights/index.md` (and one per partition)",
11327
+ "",
11328
+ "Phase 1 must create the matching `insights/{type}/index.md` page",
11329
+ "the first time it writes a file into that partition. Phase 2 must",
11330
+ "create the matching `notes/{type}/index.md` page the first time it",
11331
+ "writes a notes file into that partition. Follow the `section-index`",
11332
+ "convention: a 1\u20132 paragraph summary of the tree's purpose plus a",
11333
+ "linked listing of the partition's children. Append a row for the",
11334
+ "current meeting's basename when an index already exists.",
10755
11335
  "",
10756
11336
  "---",
10757
11337
  "",
@@ -10880,17 +11460,25 @@ function buildMeetingAnalystSubAgent(tier) {
10880
11460
  "",
10881
11461
  "## Phase 1: Extract (`meeting:extract`)",
10882
11462
  "",
10883
- "**Goal:** Read the meeting transcript and categorize all substantive content.",
11463
+ "**Goal:** Read the available meeting inputs (notes, transcript, or both)",
11464
+ "and categorize all substantive content into a structured extraction.",
10884
11465
  "",
10885
11466
  "### Steps",
10886
11467
  "",
10887
- "1. Read the transcript file specified in the issue body. Parse the",
10888
- " meeting-note frontmatter first, capturing `meeting_type` and",
10889
- " `areas`. Resolve `meeting_type` to a kind using the **Meeting type",
10890
- " handling** table; note the resolved kind in the extraction",
10891
- " frontmatter. If `meeting_type` is missing or not in the project's",
10892
- " Recognized meeting types table, treat it as `other` and flag it",
10893
- " in Open Questions.",
11468
+ "1. Identify the meeting's basename and `{internal,external}` partition",
11469
+ " from the issue body, then read **both** sibling files when they",
11470
+ " exist:",
11471
+ " - Notes: `<meetingsRoot>/notes/{type}/<basename>.md`",
11472
+ " - Transcript: `<meetingsRoot>/transcripts/{type}/<basename>.md`",
11473
+ "",
11474
+ " Use the **input-case matrix** in **Meeting file layout** to decide",
11475
+ " which case you are in (both / notes-only / transcript-only). Parse",
11476
+ " the notes frontmatter first (if a notes file exists), capturing",
11477
+ " `meeting_type`, `areas`, `source`, and `confidence`. Resolve",
11478
+ " `meeting_type` to a kind using the **Meeting type handling**",
11479
+ " table; note the resolved kind in the extraction frontmatter. If",
11480
+ " `meeting_type` is missing, treat it as `other` and flag it in",
11481
+ " Open Questions \u2014 do **not** fail-loud on missing frontmatter.",
10894
11482
  "2. Identify and categorize content into these buckets:",
10895
11483
  "",
10896
11484
  " | Bucket | What to look for |",
@@ -10905,7 +11493,10 @@ function buildMeetingAnalystSubAgent(tier) {
10905
11493
  " | **Strategic direction** | Business model changes, market positioning |",
10906
11494
  " | **Product direction** | Roadmap changes, feature prioritization |",
10907
11495
  "",
10908
- "3. Write the extraction to a markdown file with structured sections:",
11496
+ "3. Write the extraction to",
11497
+ " `<meetingsRoot>/insights/{type}/<basename>.md` (matching the",
11498
+ " basename and `{internal,external}` partition of the source",
11499
+ " notes / transcript) with structured sections:",
10909
11500
  " - Attendees",
10910
11501
  " - Decisions Made (with category and confidence: Firm / Tentative / Needs confirmation)",
10911
11502
  " - Requirements Identified (with category and priority estimate)",
@@ -10916,9 +11507,14 @@ function buildMeetingAnalystSubAgent(tier) {
10916
11507
  " - Companies of Interest (with type and context)",
10917
11508
  " - Strategic / Product Direction",
10918
11509
  "",
10919
- " Carry `meeting_type`, the resolved `meeting_kind`, and `areas`",
10920
- " into the extraction frontmatter so downstream phases can gate",
10921
- " behaviour without re-parsing the meeting note.",
11510
+ " Carry the following into the extraction frontmatter so downstream",
11511
+ " phases can gate behaviour without re-parsing the source files:",
11512
+ " `meeting_type`, the resolved `meeting_kind`, `areas`,",
11513
+ " `transcript_available: true|false` (cached from the filesystem",
11514
+ " check in step 1), and `confidence: high|medium|low` (per the",
11515
+ " input-case matrix \u2014 `high` when a transcript is present, `medium`",
11516
+ " on notes-only, never silently downgraded to `low` without an",
11517
+ " explicit reason recorded in Open Questions).",
10922
11518
  "",
10923
11519
  "4. **Apply type-specific rules.** Consult the **Meeting type handling**",
10924
11520
  " table for the resolved kind and apply its phase-1 rules in",
@@ -10932,7 +11528,14 @@ function buildMeetingAnalystSubAgent(tier) {
10932
11528
  " Interest (signal threshold still applies); capture customer",
10933
11529
  " pain points as candidate BR (not FR).",
10934
11530
  "",
10935
- "5. **Create downstream phase issues** using `gh issue create`:",
11531
+ "5. **Maintain the `insights/` tree index.** Ensure",
11532
+ " `<meetingsRoot>/insights/index.md` and",
11533
+ " `<meetingsRoot>/insights/{type}/index.md` exist (create them",
11534
+ " following the `section-index` convention if missing) and append",
11535
+ " a row for the current meeting's basename if one is not already",
11536
+ " present.",
11537
+ "",
11538
+ "6. **Create downstream phase issues** using `gh issue create`:",
10936
11539
  " - Always create a `meeting:notes` issue (blocked on this extract issue)",
10937
11540
  " - If requirements OR decisions/ADRs identified, create a `meeting:draft` issue",
10938
11541
  " (blocked on the notes issue). For kind `standup`, skip the",
@@ -10941,18 +11544,59 @@ function buildMeetingAnalystSubAgent(tier) {
10941
11544
  " - Always create a `meeting:link` issue \u2014 blocked on the draft issue if one",
10942
11545
  " was created, otherwise blocked on the notes issue",
10943
11546
  "",
10944
- "6. Commit, push, and close the extract issue.",
11547
+ "7. Commit, push, and close the extract issue.",
10945
11548
  "",
10946
11549
  "---",
10947
11550
  "",
10948
11551
  "## Phase 2: Notes (`meeting:notes`)",
10949
11552
  "",
10950
- "**Goal:** Transform the extraction into structured meeting notes.",
11553
+ "**Goal:** Produce a curated `notes/{type}/<basename>.md` file that",
11554
+ "reflects the structured meeting record. The pivot here from older",
11555
+ 'versions of this bundle: Phase 2 is no longer "author notes from',
11556
+ 'scratch." It is **enhance and verify** \u2014 restructure the existing',
11557
+ "Gemini-generated notes (when present), verify factual claims",
11558
+ "against the transcript (when present), and fall back to authoring",
11559
+ "only when no notes file exists.",
10951
11560
  "",
10952
11561
  "### Steps",
10953
11562
  "",
10954
- "1. Read the extraction file referenced in the issue body (output of Phase 1).",
10955
- "2. Write structured meeting notes with these sections:",
11563
+ "1. Read the extraction at",
11564
+ " `<meetingsRoot>/insights/{type}/<basename>.md` (output of Phase 1).",
11565
+ " Read the `transcript_available` and `confidence` fields from its",
11566
+ " frontmatter. Then determine which input-case branch applies by",
11567
+ " checking the filesystem for the sibling notes and transcript",
11568
+ " files.",
11569
+ "2. **Branch by input case** (see the input-case matrix in the",
11570
+ " **Meeting file layout** section):",
11571
+ "",
11572
+ " - **Both notes and transcript present** (enhance + verify):",
11573
+ " Read the existing `notes/{type}/<basename>.md` in place.",
11574
+ " Restructure it into the canonical section layout below.",
11575
+ " Verify every factual claim against the transcript \u2014 when the",
11576
+ " transcript contradicts the notes, the transcript wins and the",
11577
+ " notes are corrected. Fill in gaps the transcript reveals were",
11578
+ " missed. Overwrite the notes file in place; preserve the",
11579
+ " existing frontmatter and update only the fields you changed",
11580
+ " (e.g. flip `source: gemini` to `source: hybrid` once the agent",
11581
+ " has edited the file). Set `confidence: high`.",
11582
+ "",
11583
+ " - **Notes only** (light structural cleanup):",
11584
+ " Read the existing `notes/{type}/<basename>.md`. Restructure it",
11585
+ " into the canonical section layout below. Do **not** invent",
11586
+ " dialogue or claims the notes do not record. Do **not** mark",
11587
+ " `confidence: high` \u2014 keep `confidence: medium` and record the",
11588
+ ' reason ("no transcript available for verification") in the',
11589
+ " notes' Open Questions section so downstream phases know to",
11590
+ " treat downstream requirement drafts as `Tentative`.",
11591
+ "",
11592
+ " - **Transcript only** (author from scratch):",
11593
+ " The notes file does not yet exist. Author",
11594
+ " `notes/{type}/<basename>.md` from the transcript using the",
11595
+ " section layout below. Populate frontmatter with",
11596
+ " `source: gemini` if the transcript is a Gemini transcript,",
11597
+ " otherwise `source: unknown`. Set `confidence: high`.",
11598
+ "",
11599
+ "3. Canonical notes section layout (applied in every branch above):",
10956
11600
  " - Meeting metadata (title, date, attendees)",
10957
11601
  " - Agenda / topics covered",
10958
11602
  " - Key Discussion Points (organized by topic)",
@@ -10960,7 +11604,13 @@ function buildMeetingAnalystSubAgent(tier) {
10960
11604
  " - Action Items (table: who, what, when)",
10961
11605
  " - Open Questions",
10962
11606
  " - Follow-up items",
10963
- "3. Commit, push, and close the notes issue.",
11607
+ "4. **Maintain the `notes/` tree index.** Ensure",
11608
+ " `<meetingsRoot>/notes/index.md` and",
11609
+ " `<meetingsRoot>/notes/{type}/index.md` exist (create them",
11610
+ " following the `section-index` convention if missing) and append",
11611
+ " a row for the current meeting's basename if one is not already",
11612
+ " present.",
11613
+ "5. Commit, push, and close the notes issue.",
10964
11614
  "",
10965
11615
  "---",
10966
11616
  "",
@@ -10973,7 +11623,8 @@ function buildMeetingAnalystSubAgent(tier) {
10973
11623
  "",
10974
11624
  "### Steps",
10975
11625
  "",
10976
- "1. Read the extraction file from Phase 1.",
11626
+ "1. Read the extraction file from Phase 1 at",
11627
+ " `<meetingsRoot>/insights/{type}/<basename>.md`.",
10977
11628
  "2. Check existing requirement registries and ADR registries for duplicates.",
10978
11629
  "3. Draft requirement proposals with:",
10979
11630
  " - Category (FR, BR, NFR, etc.)",
@@ -10997,7 +11648,8 @@ function buildMeetingAnalystSubAgent(tier) {
10997
11648
  "",
10998
11649
  "### Steps",
10999
11650
  "",
11000
- "1. Read the drafts from Phase 3 (if they exist) and the extraction from Phase 1.",
11651
+ "1. Read the drafts from Phase 3 (if they exist) and the extraction",
11652
+ " from Phase 1 at `<meetingsRoot>/insights/{type}/<basename>.md`.",
11001
11653
  " Re-read the extraction frontmatter for `meeting_kind` and `areas`,",
11002
11654
  " then build the area-gate: the set of `docRoot` sub-folders that",
11003
11655
  " are in scope for direct edits on this meeting. Apply the rules in",
@@ -11026,12 +11678,15 @@ function buildMeetingAnalystSubAgent(tier) {
11026
11678
  " sprint-plan doc's area is in-scope, or always when no",
11027
11679
  " `meetingAreas` are declared); for the kind `review`, mark",
11028
11680
  " completed tasks in that same doc.",
11029
- "7. **Update the extraction file** with a `## Downstream Artifacts` section",
11030
- " listing every issue and document created from this meeting. Note",
11031
- " any items that were deferred to follow-up issues because of the",
11032
- " area gate.",
11033
- "8. **Update the meeting notes** with a `## Related Issues` section listing",
11034
- " all issues created from this meeting.",
11681
+ "7. **Update the extraction file** at",
11682
+ " `<meetingsRoot>/insights/{type}/<basename>.md` with a",
11683
+ " `## Downstream Artifacts` section listing every issue and",
11684
+ " document created from this meeting. Note any items that were",
11685
+ " deferred to follow-up issues because of the area gate.",
11686
+ "8. **Update the meeting notes** at",
11687
+ " `<meetingsRoot>/notes/{type}/<basename>.md` with a",
11688
+ " `## Related Issues` section listing all issues created from this",
11689
+ " meeting.",
11035
11690
  "9. Comment on the parent extract issue with a summary linking to all",
11036
11691
  " created artifacts.",
11037
11692
  "10. Commit and push (if any file changes were made), then close the link issue.",
@@ -11065,21 +11720,27 @@ var processMeetingSkill = {
11065
11720
  agent: "meeting-analyst",
11066
11721
  platforms: { cursor: { exclude: true } },
11067
11722
  instructions: [
11068
- "# Process Meeting Transcript",
11723
+ "# Process Meeting",
11069
11724
  "",
11070
- "Kick off meeting transcript processing by executing Phase 1 (Extract)",
11071
- "and creating downstream phase issues for the remaining phases.",
11725
+ "Kick off meeting processing by executing Phase 1 (Extract) and",
11726
+ "creating downstream phase issues for the remaining phases. Inputs",
11727
+ "live in the **sibling-tree layout**: pass any file from",
11728
+ "`<meetingsRoot>/notes/{type}/` or `<meetingsRoot>/transcripts/{type}/`",
11729
+ "(both, notes-only, and transcript-only are all valid input cases).",
11072
11730
  "",
11073
11731
  "## Usage",
11074
11732
  "",
11075
- "/process-meeting <path-to-transcript>",
11733
+ "/process-meeting <path-to-notes-or-transcript>",
11076
11734
  "",
11077
11735
  "## Steps",
11078
11736
  "",
11079
- "1. Read the provided transcript file",
11080
- "2. Execute Phase 1 (Extract) \u2014 categorize transcript content into",
11737
+ "1. Identify the meeting's basename and `{internal,external}`",
11738
+ " partition from the provided path, then read **every** sibling",
11739
+ " that exists (notes file and/or transcript file)",
11740
+ "2. Execute Phase 1 (Extract) \u2014 categorize the available content into",
11081
11741
  " decisions, requirements, action items, open questions, and more",
11082
- "3. Write the extraction to a markdown file",
11742
+ "3. Write the extraction to",
11743
+ " `<meetingsRoot>/insights/{type}/<basename>.md`",
11083
11744
  "4. Create downstream phase issues using `gh issue create`:",
11084
11745
  " - `meeting:notes` issue (always)",
11085
11746
  " - `meeting:draft` issue (if requirements or decisions were found)",
@@ -11089,12 +11750,15 @@ var processMeetingSkill = {
11089
11750
  "",
11090
11751
  "## Input",
11091
11752
  "",
11092
- "Provide a path to a meeting transcript file (text or markdown).",
11093
- "The transcript should contain speaker-attributed dialogue.",
11753
+ "Provide a path to a file in either `notes/` or `transcripts/`. The",
11754
+ "agent will look up the sibling automatically via basename match. A",
11755
+ "missing sibling is a normal case (notes-only or transcript-only) \u2014",
11756
+ "the agent handles all three input cases.",
11094
11757
  "",
11095
11758
  "## Output",
11096
11759
  "",
11097
- "- An extraction markdown file with categorized meeting content",
11760
+ "- A structured extraction at",
11761
+ " `<meetingsRoot>/insights/{type}/<basename>.md`",
11098
11762
  "- Phase issues with `meeting:*` labels for downstream agent sessions",
11099
11763
  " to pick up (notes, draft, link)"
11100
11764
  ].join("\n")
@@ -11112,11 +11776,30 @@ function buildMeetingAnalysisBundle(tier = AGENT_MODEL.BALANCED) {
11112
11776
  content: [
11113
11777
  "# Meeting Processing Workflow",
11114
11778
  "",
11115
- "Use `/process-meeting <path>` to process a meeting transcript through a",
11779
+ "Use `/process-meeting <path>` to process a meeting through a",
11116
11780
  "4-phase pipeline (extract \u2192 notes \u2192 draft \u2192 link). Each phase runs as a",
11117
11781
  "separate agent session tracked by a GitHub issue with a `meeting:*` label.",
11118
11782
  "See the `meeting-analyst` agent definition for full workflow details.",
11119
11783
  "",
11784
+ "Meeting artifacts live in **three parallel trees** rooted at the",
11785
+ "project's meetings root:",
11786
+ "",
11787
+ "- `transcripts/{internal,external}/` \u2014 verbatim transcripts (evidence)",
11788
+ "- `notes/{internal,external}/` \u2014 curated narrative summary",
11789
+ " (input + Phase 2 output)",
11790
+ "- `insights/{internal,external}/` \u2014 structured extraction (Phase 1 output)",
11791
+ "",
11792
+ "The three trees share identical basenames so a meeting's siblings",
11793
+ "are looked up by filename, not by frontmatter cross-references.",
11794
+ "Phase 1 accepts every input case (notes + transcript, notes only,",
11795
+ "or transcript only) and Phase 2 enhances the notes file in place",
11796
+ "rather than authoring it from scratch.",
11797
+ "",
11798
+ "Notes and insights files may carry optional frontmatter fields",
11799
+ "`slug`, `source`, `transcript_available`, and `confidence`",
11800
+ "alongside the existing `meeting_type` and `areas` fields. Missing",
11801
+ "frontmatter is treated as a normal input case \u2014 never fail-loud.",
11802
+ "",
11120
11803
  "Meeting notes may declare a `meeting_type` (one of the project's",
11121
11804
  "recognized types) and an `areas: [...]` list. The `meeting_type`",
11122
11805
  "resolves to a generic kind \u2014 `planning` / `review` / `brainstorm` /",
@@ -15471,6 +16154,17 @@ function buildPeopleProfileAnalystSubAgent(paths, issueDefaults, tier) {
15471
16154
  "full background, and do not rewrite the profile body for facts that",
15472
16155
  "have not changed.",
15473
16156
  "",
16157
+ "**Temporal framing.** Refreshes follow the same grep-`as of `-and-",
16158
+ "re-verify pattern documented for `company:refresh` in the",
16159
+ "`company-profile-analyst` bundle (Phase 5, steps 3 and 5): build",
16160
+ "the list of qualified claims, classify each against the cadence",
16161
+ "table in the `temporal-framing-convention` rule (90d for current",
16162
+ "role / employer / tenure, falling back to 180d for slower-decay",
16163
+ "claims), re-verify every claim past cadence, and update the",
16164
+ "qualifier date on every claim that still holds. Treat a profile",
16165
+ "with no `as of ` qualifiers as needing back-fill before any other",
16166
+ "verification.",
16167
+ "",
15474
16168
  "### Steps",
15475
16169
  "",
15476
16170
  "1. **Read the existing profile** at the path referenced in the issue",
@@ -29173,6 +29867,17 @@ var AgentConfig = class _AgentConfig extends Component8 {
29173
29867
  });
29174
29868
  }
29175
29869
  }
29870
+ const resolvedTemporalFraming = validateTemporalFramingConfig(
29871
+ this.options.temporalFraming
29872
+ );
29873
+ if (resolvedTemporalFraming.enabled && resolvedTemporalFraming.emitChecker) {
29874
+ new TextFile4(this, ".claude/procedures/check-temporal-framing.sh", {
29875
+ lines: renderTemporalFramingCheckerScript(
29876
+ resolvedTemporalFraming
29877
+ ).split("\n"),
29878
+ executable: true
29879
+ });
29880
+ }
29176
29881
  const platforms = this.resolvePlatforms();
29177
29882
  const rules = this.resolveRules();
29178
29883
  const skills = this.resolveSkills();
@@ -29520,6 +30225,23 @@ ${hook}`
29520
30225
  });
29521
30226
  }
29522
30227
  }
30228
+ const resolvedTemporalFramingForRules = resolveTemporalFraming(
30229
+ this.options.temporalFraming
30230
+ );
30231
+ if (this.options.temporalFraming) {
30232
+ const temporalFramingRule = ruleMap.get("temporal-framing-convention");
30233
+ if (temporalFramingRule) {
30234
+ const temporalFramingContent = renderTemporalFramingRuleContent(
30235
+ resolvedTemporalFramingForRules
30236
+ );
30237
+ if (temporalFramingContent !== temporalFramingRule.content) {
30238
+ ruleMap.set("temporal-framing-convention", {
30239
+ ...temporalFramingRule,
30240
+ content: temporalFramingContent
30241
+ });
30242
+ }
30243
+ }
30244
+ }
29523
30245
  const tierExamples = this.options.features?.sourceTierExamples;
29524
30246
  if (_AgentConfig.hasActiveTierExamples(tierExamples)) {
29525
30247
  const sourceRule = ruleMap.get("source-quality-verification");
@@ -34300,6 +35022,10 @@ export {
34300
35022
  DEFAULT_STATE_FILE_PATH,
34301
35023
  DEFAULT_STATUS_LABELS,
34302
35024
  DEFAULT_TEARDOWN_BRANCH_PATTERNS,
35025
+ DEFAULT_TEMPORAL_FRAMING_CADENCES,
35026
+ DEFAULT_TEMPORAL_FRAMING_EMIT_CHECKER,
35027
+ DEFAULT_TEMPORAL_FRAMING_ENABLED,
35028
+ DEFAULT_TEMPORAL_FRAMING_PATHS,
34303
35029
  DEFAULT_TYPE_LABELS,
34304
35030
  DEFAULT_UNBLOCK_COMMENT_TEMPLATE,
34305
35031
  DEFAULT_UNBLOCK_DEPENDENTS_ENABLED,
@@ -34331,6 +35057,7 @@ export {
34331
35057
  SUPPRESSED_WORKFLOW_RULE_NAMES,
34332
35058
  SampleLang,
34333
35059
  StarlightProject,
35060
+ TEMPORAL_FRAMING_CATEGORY_VALUES,
34334
35061
  TIER_AWARE_BUNDLE_NAMES,
34335
35062
  TestRunner,
34336
35063
  TsDocCoverageKind,
@@ -34454,6 +35181,8 @@ export {
34454
35181
  renderSkillEvalsRunnerScript,
34455
35182
  renderSourceTierExamples,
34456
35183
  renderStubIndexConventionRuleContent,
35184
+ renderTemporalFramingCheckerScript,
35185
+ renderTemporalFramingRuleContent,
34457
35186
  renderUnblockDependentsScript,
34458
35187
  renderUnblockDependentsSection,
34459
35188
  requirementsAnalystBundle,
@@ -34479,6 +35208,7 @@ export {
34479
35208
  resolveSharedEditing,
34480
35209
  resolveSkillEvals,
34481
35210
  resolveTemplateVariables,
35211
+ resolveTemporalFraming,
34482
35212
  resolveTypeScriptProjectOutdir,
34483
35213
  resolveUnblockDependents,
34484
35214
  runScan,
@@ -34500,6 +35230,7 @@ export {
34500
35230
  validateSharedEditingConfig,
34501
35231
  validateSkillEvalsConfig,
34502
35232
  validateStarlightSingleton,
35233
+ validateTemporalFramingConfig,
34503
35234
  validateUnblockDependentsConfig,
34504
35235
  vitestBundle
34505
35236
  };